diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e699290a..9e70fc2b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,6 +37,10 @@ jobs: - name: yarn install run: | YARN_CHECKSUM_BEHAVIOR=update yarn install + cd cli + YARN_CHECKSUM_BEHAVIOR=update yarn install + cd .. + - name: yarn lint env: diff --git a/.gitignore b/.gitignore index fcee2d74..2d8fedfc 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,13 @@ yarn-error.log !.yarn/releases !.yarn/sdks !.yarn/versions +cli/.pnp.* +cli/.yarn/* +!cli/.yarn/patches +!cli/.yarn/plugins +!cli/.yarn/releases +!cli/.yarn/sdks +!cli/.yarn/versions # App specific # diff --git a/api/routes/auth/message.mjs b/api/routes/auth/message.mjs index 7e5119d9..5fc0b9f8 100644 --- a/api/routes/auth/message.mjs +++ b/api/routes/auth/message.mjs @@ -1,12 +1,16 @@ import express from 'express' -import { tools } from 'nanocurrency-web' import BigNumber from 'bignumber.js' -import { rpc, verify_nano_community_message_signature } from '#common' +import { + rpc, + verify_nano_community_message_signature, + encode_nano_address +} from '#common' import { ACCOUNT_TRACKING_MINIMUM_BALANCE, REPRESENTATIVE_TRACKING_MINIMUM_VOTING_WEIGHT } from '#constants' +import { process_community_message } from '#libs-server' const router = express.Router() @@ -26,9 +30,9 @@ router.post('/?', async (req, res) => { public_key, operation, content, - tags, + tags = [], - references, + references = [], created_at, @@ -36,62 +40,68 @@ router.post('/?', async (req, res) => { } = message if (version !== 1) { - return res.status(400).send('Invalid message version') + return res.status(400).json({ error: 'Invalid message version' }) } // entry_id must be null or 32 byte hash if (entry_id && entry_id.length !== 64) { - return res.status(400).send('Invalid entry_id') + return res.status(400).json({ error: 'Invalid entry_id' }) } // chain_id must be null or 32 byte hash if (chain_id && chain_id.length !== 64) { - return res.status(400).send('Invalid chain_id') + return res.status(400).json({ error: 'Invalid chain_id' }) } // entry_clock must be null or positive integer if (entry_clock && entry_clock < 0) { - return res.status(400).send('Invalid entry_clock') + return res.status(400).json({ error: 'Invalid entry_clock' }) } // chain_clock must be null or positive integer if (chain_clock && chain_clock < 0) { - return res.status(400).send('Invalid chain_clock') + return res.status(400).json({ error: 'Invalid chain_clock' }) } // public_key must be 32 byte hash if (public_key.length !== 64) { - return res.status(400).send('Invalid public_key') + return res.status(400).json({ error: 'Invalid public_key' }) } // operation must be SET or DELETE - if (operation !== 'SET' && operation !== 'DELETE') { - return res.status(400).send('Invalid operation') + const allowed_operations = [ + 'SET', + 'SET_ACCOUNT_META', + 'SET_REPRESENTATIVE_META', + 'SET_BLOCK_META' + ] + if (!allowed_operations.includes(operation)) { + return res.status(400).json({ error: 'Invalid operation' }) } // content must be null or string if (content && typeof content !== 'string') { - return res.status(400).send('Invalid content') + return res.status(400).json({ error: 'Invalid content' }) } // tags must be null or array of strings if (tags && !Array.isArray(tags)) { - return res.status(400).send('Invalid tags') + return res.status(400).json({ error: 'Invalid tags' }) } // references must be null or array of strings if (references && !Array.isArray(references)) { - return res.status(400).send('Invalid references') + return res.status(400).json({ error: 'Invalid references' }) } // created_at must be null or positive integer if (created_at && created_at < 0) { - return res.status(400).send('Invalid created_at') + return res.status(400).json({ error: 'Invalid created_at' }) } // signature must be 64 byte hash if (signature.length !== 128) { - return res.status(400).send('Invalid signature') + return res.status(400).json({ error: 'Invalid signature' }) } // validate signature @@ -109,39 +119,35 @@ router.post('/?', async (req, res) => { signature }) if (!is_valid_signature) { - return res.status(400).send('Invalid signature') + return res.status(400).json({ error: 'Invalid signature' }) } // public_key can be a linked keypair or an existing nano account - const linked_accounts = await db('account_keys') + + const linked_account = await db('account_keys') .select('account') .where({ public_key }) .whereNull('revoked_at') - const nano_account = tools.publicKeyToAddress(public_key) + .first() - const all_accounts = [ - ...linked_accounts.map((row) => row.account), - nano_account - ] + const message_nano_account = linked_account + ? linked_account.account + : encode_nano_address({ + public_key_buf: Buffer.from(public_key, 'hex') + }) - const accounts_info = [] - for (const account of all_accounts) { - const account_info = await rpc.accountInfo({ account }) - if (account_info) { - accounts_info.push(account_info) - } - } + const account_info = await rpc.accountInfo({ + account: message_nano_account + }) // check if any of the accounts have a balance beyond the tracking threshold - const has_balance = accounts_info.some((account_info) => - new BigNumber(account_info.balance).gte(ACCOUNT_TRACKING_MINIMUM_BALANCE) + const has_balance = new BigNumber(account_info?.balance || 0).gte( + ACCOUNT_TRACKING_MINIMUM_BALANCE ) // check if any of the accounts have weight beyond the tracking threshold - const has_weight = accounts_info.some((account_info) => - new BigNumber(account_info.weight).gte( - REPRESENTATIVE_TRACKING_MINIMUM_VOTING_WEIGHT - ) + const has_weight = new BigNumber(account_info?.weight || 0).gte( + REPRESENTATIVE_TRACKING_MINIMUM_VOTING_WEIGHT ) if (has_balance || has_weight) { @@ -169,6 +175,15 @@ router.post('/?', async (req, res) => { .merge() } + try { + await process_community_message({ + message, + message_account: message_nano_account + }) + } catch (error) { + logger(error) + } + res.status(200).send({ version, @@ -189,7 +204,7 @@ router.post('/?', async (req, res) => { } catch (error) { console.log(error) logger(error) - res.status(500).send('Internal server error') + res.status(500).json({ error: 'Internal server error' }) } }) diff --git a/api/routes/auth/register.mjs b/api/routes/auth/register.mjs index 0f478961..1a45fceb 100644 --- a/api/routes/auth/register.mjs +++ b/api/routes/auth/register.mjs @@ -1,11 +1,16 @@ import express from 'express' -import nano from 'nanocurrency' import ed25519 from '@trashman/ed25519-blake2b' -import { verify_nano_community_link_key_signature } from '#common' +import { + verify_nano_community_link_key_signature, + is_nano_address_valid, + decode_nano_address +} from '#common' const router = express.Router() const USERNAME_RE = /^[A-Za-z][a-zA-Z0-9_]+$/ +const PUBLIC_KEY_RE = /^[0-9a-fA-F]{64}$/ +const SIGNATURE_RE = /^[0-9a-fA-F]{128}$/ router.post('/?', async (req, res) => { const { logger, db } = req.app.locals @@ -19,7 +24,7 @@ router.post('/?', async (req, res) => { const { public_key, signature, username } = req.body - if (!nano.checkKey(public_key)) { + if (typeof public_key !== 'string' || !PUBLIC_KEY_RE.test(public_key)) { return res.status(401).send({ error: 'invalid public_key param' }) } @@ -27,7 +32,7 @@ router.post('/?', async (req, res) => { return res.status(401).send({ error: 'invalid username param' }) } - if (!nano.checkSignature(signature)) { + if (typeof signature !== 'string' || !SIGNATURE_RE.test(signature)) { return res.status(401).send({ error: 'invalid signature' }) } @@ -78,19 +83,21 @@ router.post('/key/?', async (req, res) => { const { public_key, signature, account } = req.body - if (!nano.checkKey(public_key)) { + if (typeof public_key !== 'string' || !PUBLIC_KEY_RE.test(public_key)) { return res.status(401).send({ error: 'invalid public_key param' }) } - if (!nano.checkAddress(account)) { + if (!is_nano_address_valid(account)) { return res.status(401).send({ error: 'invalid account param' }) } - if (!nano.checkSignature(signature)) { + if (typeof signature !== 'string' || !SIGNATURE_RE.test(signature)) { return res.status(401).send({ error: 'invalid signature' }) } - const account_public_key = nano.derivePublicKey(account) + const { public_key: account_public_key } = decode_nano_address({ + address: account + }) const valid_signature = verify_nano_community_link_key_signature({ linked_public_key: public_key, nano_account: account, diff --git a/api/routes/auth/revoke.mjs b/api/routes/auth/revoke.mjs index df6fd0dd..aac1e455 100644 --- a/api/routes/auth/revoke.mjs +++ b/api/routes/auth/revoke.mjs @@ -1,52 +1,37 @@ import express from 'express' -import nano from 'nanocurrency' -import { verify_nano_community_revoke_key_signature } from '#common' +import { + verify_nano_community_revoke_key_signature, + decode_nano_address +} from '#common' const router = express.Router() +const PUBLIC_KEY_RE = /^[0-9a-fA-F]{64}$/ +const SIGNATURE_RE = /^[0-9a-fA-F]{128}$/ router.post('/key/?', async (req, res) => { const { logger, db } = req.app.locals try { - const required = ['account', 'public_key', 'signature'] + const required = ['public_key', 'signature'] for (const prop of required) { if (!req.body[prop]) { return res.status(400).send({ error: `missing ${prop} param` }) } } - const { account, public_key, signature } = req.body + const { public_key, signature } = req.body - if (!nano.checkAddress(account)) { - return res.status(401).send({ error: 'invalid account param' }) - } - - if (!nano.checkKey(public_key)) { + if (typeof public_key !== 'string' || !PUBLIC_KEY_RE.test(public_key)) { return res.status(401).send({ error: 'invalid public_key param' }) } - if (!nano.checkSignature(signature)) { - return res.status(401).send({ error: 'invalid signature' }) - } - - const account_public_key = nano.derivePublicKey(account) - const valid_signature = verify_nano_community_revoke_key_signature({ - linked_public_key: public_key, - nano_account: account, - nano_account_public_key: account_public_key, - signature - }) - if (!valid_signature) { - return res.status(401).send({ error: 'invalid signature' }) + if (typeof signature !== 'string' || !SIGNATURE_RE.test(signature)) { + return res.status(401).send({ error: 'invalid signature param' }) } - const linked_key = await db('account_keys') - .where({ account, public_key }) - .first() + const linked_key = await db('account_keys').where({ public_key }).first() if (!linked_key) { - return res - .status(401) - .send({ error: `key ${public_key} not linked to account ${account}` }) + return res.status(401).send({ error: `key ${public_key} not found` }) } if (linked_key.revoked_at) { @@ -55,13 +40,33 @@ router.post('/key/?', async (req, res) => { .send({ error: `key ${public_key} already revoked` }) } + const valid_signing_key_signature = + verify_nano_community_revoke_key_signature({ + linked_public_key: public_key, + either_public_key: public_key, + signature + }) + const { public_key: account_public_key } = decode_nano_address({ + address: linked_key.account + }) + const valid_account_key_signature = + verify_nano_community_revoke_key_signature({ + linked_public_key: public_key, + either_public_key: account_public_key, + signature + }) + + if (!valid_signing_key_signature && !valid_account_key_signature) { + return res.status(401).send({ error: 'invalid signature' }) + } + const revoked_at = Math.floor(Date.now() / 1000) await db('account_keys') .update({ revoked_at, revoke_signature: signature }) - .where({ account, public_key }) + .where({ account: linked_key.account, public_key }) res.status(200).send({ - account, + account: linked_key.account, public_key, signature, created_at: linked_key.created_at, diff --git a/cli/index.mjs b/cli/index.mjs new file mode 100755 index 00000000..90258164 --- /dev/null +++ b/cli/index.mjs @@ -0,0 +1,434 @@ +#!/usr/bin/env node + +import crypto from 'crypto' +import process from 'process' + +import yargs from 'yargs' +import { hideBin } from 'yargs/helpers' +import inquirer from 'inquirer' +import fetch, { Request } from 'node-fetch' +import ed25519 from '@trashman/ed25519-blake2b' + +const is_test = process.env.NODE_ENV === 'test' + +const base_url = is_test ? 'http://localhost:8080' : 'https://nano.community' + +function sign_nano_community_link_key({ + linked_public_key, + nano_account, + nano_account_private_key, + nano_account_public_key +}) { + if (!linked_public_key) { + throw new Error('linked_public_key is required') + } + + if (!nano_account) { + throw new Error('nano_account is required') + } + + if (!nano_account_private_key) { + throw new Error('nano_account_private_key is required') + } + + if (!nano_account_public_key) { + throw new Error('nano_account_public_key is required') + } + + const data = Buffer.from(['LINK', nano_account, linked_public_key]) + + const message_hash = ed25519.hash(data) + + return ed25519.sign( + message_hash, + nano_account_private_key, + nano_account_public_key + ) +} + +function sign_nano_community_revoke_key({ + linked_public_key, + either_private_key, + either_public_key +}) { + if (!linked_public_key) { + throw new Error('linked_public_key is required') + } + + if (!either_private_key) { + throw new Error('either_private_key is required') + } + + if (!either_public_key) { + throw new Error('either_public_key is required') + } + + const data = Buffer.from(['REVOKE', linked_public_key]) + + const message_hash = ed25519.hash(data) + + return ed25519.sign(message_hash, either_private_key, either_public_key) +} + +function sign_nano_community_message(message, private_key) { + const { + entry_id, + chain_id, + entry_clock, + chain_clock, + public_key, + operation, + content, + tags, + references, + created_at + } = message + + const data = Buffer.from([ + entry_id, + chain_id, + entry_clock, + chain_clock, + public_key, + operation, + content, + tags, + references, + created_at + ]) + + const message_hash = ed25519.hash(data) + + return ed25519.sign(message_hash, private_key, public_key) +} + +function encode_nano_base32(view) { + const length = view.length + const leftover = (length * 8) % 5 + const offset = leftover === 0 ? 0 : 5 - leftover + const alphabet = '13456789abcdefghijkmnopqrstuwxyz' + + let value = 0 + let output = '' + let bits = 0 + + for (let i = 0; i < length; i++) { + value = (value << 8) | view[i] + bits += 8 + + while (bits >= 5) { + output += alphabet[(value >>> (bits + offset - 5)) & 31] + bits -= 5 + } + } + + if (bits > 0) { + output += alphabet[(value << (5 - (bits + offset))) & 31] + } + + return output +} + +function encode_nano_address({ public_key_buf, prefix = 'nano_' }) { + const encoded_public_key = encode_nano_base32(public_key_buf) + const checksum = ed25519.hash(public_key_buf, 5).reverse() + const encoded_checksum = encode_nano_base32(checksum) + return prefix + encoded_public_key + encoded_checksum +} + +async function request(options) { + const request = new Request(options.url, { + timeout: 20000, + ...options + }) + const response = await fetch(request) + + if (response.status >= 200 && response.status < 300) { + return response.json() + } else { + const res = await response.json() + const error = new Error(res.error || response.statusText) + error.response = response + throw error + } +} + +async function load_private_key() { + let private_key = process.env.NC_CLI_NANO_PRIVATE_KEY + if (private_key) { + console.log('Private key found in environment variable.') + } else { + console.log( + 'No private key found in environment variable (NC_CLI_NANO_PRIVATE_KEY).' + ) + // Restore stdin for inquirer + const answers = await inquirer.prompt([ + { + type: 'password', + name: 'private_key', + message: 'Please enter your private key:' + } + ]) + private_key = answers.private_key + } + + const public_key_buf = ed25519.publicKey(Buffer.from(private_key, 'hex')) + const nano_account_address = encode_nano_address({ public_key_buf }) + return { + private_key, + public_key: public_key_buf.toString('hex'), + nano_account_address + } +} + +const add_signing_key = { + command: 'add-signing-key', + describe: 'Add a new signing key', + handler: async () => { + const { private_key, public_key, nano_account_address } = + await load_private_key() + + const linked_private_key = crypto.randomBytes(32) + const linked_public_key = ed25519.publicKey(linked_private_key) + + const signature = sign_nano_community_link_key({ + linked_public_key, + nano_account: nano_account_address, + nano_account_private_key: private_key, + nano_account_public_key: public_key + }) + + const payload = { + public_key: linked_public_key.toString('hex'), + signature: signature.toString('hex'), + account: nano_account_address + } + + try { + const response = await request({ + url: `${base_url}/api/auth/register/key`, + method: 'POST', + body: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/json' + } + }) + console.log('Key registration successful:', response) + } catch (error) { + console.error(`Failed to register key: ${error.message || error}`) + } + + console.log({ + linked_public_key: linked_public_key.toString('hex'), + linked_private_key: linked_private_key.toString('hex') + }) + } +} + +const revoke_signing_key = { + command: 'revoke-signing-key ', + describe: 'Revoke an existing signing key', + builder: (yargs) => + yargs.positional('linked_public_key', { + describe: 'Public key of the signing key to revoke', + type: 'string' + }), + handler: async ({ linked_public_key }) => { + const { private_key, public_key } = await load_private_key() + + // Confirm revocation + const answers = await inquirer.prompt([ + { + type: 'confirm', + name: 'confirm_revoke', + message: `Are you sure you want to revoke the signing key: ${public_key}?`, + default: false + } + ]) + + if (answers.confirm_revoke) { + console.log('Revoking signing key...') + const signature = sign_nano_community_revoke_key({ + linked_public_key, + either_private_key: private_key, + either_public_key: public_key + }) + + const payload = { + public_key: linked_public_key.toString('hex'), + signature: signature.toString('hex') + } + + try { + const response = await request({ + url: `${base_url}/api/auth/revoke/key`, + method: 'POST', + body: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/json' + } + }) + console.log('Key revocation successful:', response) + } catch (error) { + console.error(`Failed to revoke key: ${error.message || error}`) + } + } else { + console.log('Signing key revocation cancelled.') + } + } +} + +const update_rep_meta = { + command: 'update-rep-meta', + describe: 'Update representative metadata', + handler: async () => await send_message_handler('update-rep-meta') +} + +const update_account_meta = { + command: 'update-account-meta', + describe: 'Update account metadata', + handler: async () => await send_message_handler('update-account-meta') +} + +const update_block_meta = { + command: 'update-block-meta ', + describe: 'Update block metadata', + builder: (yargs) => + yargs.positional('block_hash', { + describe: 'Block hash for update-block-meta type', + type: 'string' + }), + handler: async ({ block_hash }) => + await send_message_handler('update-block-meta', block_hash) +} + +async function send_message_handler(type, block_hash = null) { + const { private_key, public_key } = await load_private_key() + + let message_content_prompts = [] + console.log(`Sending message of type: ${type}`) + switch (type) { + case 'update-rep-meta': + message_content_prompts = [ + { name: 'alias', message: 'Alias:' }, + { name: 'description', message: 'Description:' }, + { name: 'donation_address', message: 'Donation Address:' }, + { name: 'cpu_model', message: 'CPU Model:' }, + { name: 'cpu_cores', message: 'CPU Cores:' }, + { name: 'ram', message: 'RAM Amount (GB):' }, + { name: 'reddit', message: 'Reddit Username:' }, + { name: 'twitter', message: 'Twitter Username:' }, + { name: 'discord', message: 'Discord Username:' }, + { name: 'github', message: 'GitHub Username:' }, + { name: 'email', message: 'Email:' }, + { name: 'website', message: 'Website URL:' } + ] + break + case 'update-account-meta': + message_content_prompts = [{ name: 'alias', message: 'Alias:' }] + break + case 'update-block-meta': + if (!block_hash) { + console.error('Block hash is required for update-block-meta type') + return + } + message_content_prompts = [{ name: 'note', message: 'Note:' }] + break + default: + console.error('Unknown message type') + return + } + + const message_content = await inquirer.prompt(message_content_prompts) + let confirm_edit = false + do { + console.log('Please review your message content:', message_content) + confirm_edit = await inquirer.prompt([ + { + type: 'confirm', + name: 'edit', + message: 'Would you like to edit any field?', + default: false + } + ]) + confirm_edit = confirm_edit.edit + if (confirm_edit) { + const field_to_edit = await inquirer.prompt([ + { + type: 'list', + name: 'field', + message: 'Which field would you like to edit?', + choices: message_content_prompts.map((prompt) => prompt.name) + } + ]) + const new_value = await inquirer.prompt([ + { + name: 'new_value', + message: `Enter new value for ${field_to_edit.field}:` + } + ]) + message_content[field_to_edit.field] = new_value.new_value + } + } while (confirm_edit) + + // Include block_hash in message for update-block-meta type + if (type === 'update-block-meta') { + message_content.block_hash = block_hash + } + + // Adjust operation to match allowed operations + let operation = '' + switch (type) { + case 'update-rep-meta': + operation = 'SET_REPRESENTATIVE_META' + break + case 'update-account-meta': + operation = 'SET_ACCOUNT_META' + break + case 'update-block-meta': + operation = 'SET_BLOCK_META' + break + } + + const message = { + version: 1, + created_at: Math.floor(Date.now() / 1000), + public_key, // public key of signing key + operation, + content: JSON.stringify(message_content) + } + + const signature = sign_nano_community_message(message, private_key) + const payload = { + ...message, + signature: signature.toString('hex') + } + + try { + const response = await request({ + url: `${base_url}/api/auth/message`, + method: 'POST', + body: JSON.stringify({ message: payload }), + headers: { + 'Content-Type': 'application/json' + } + }) + console.log('Message sent successful:', response) + } catch (error) { + console.error(`Failed to send message: ${error.message || error}`) + } +} + +// eslint-disable-next-line no-unused-expressions +yargs(hideBin(process.argv)) + .scriptName('nano-community') + .usage('$0 [args]') + .command(add_signing_key) + .command(revoke_signing_key) + .command(update_rep_meta) + .command(update_account_meta) + .command(update_block_meta) + .demandCommand(1, 'You must provide at least one command.') + .help('h') + .wrap(100) + .alias('h', 'help').argv diff --git a/cli/package.json b/cli/package.json new file mode 100644 index 00000000..e4f6e2fa --- /dev/null +++ b/cli/package.json @@ -0,0 +1,28 @@ +{ + "name": "nano-community-cli", + "version": "0.0.1", + "description": "CLI for Nano.Community Messages", + "main": "index.mjs", + "bin": { + "nano-community": "./index.mjs" + }, + "keywords": [ + "nano.community", + "nano", + "crypto", + "blockchain", + "cli" + ], + "author": "Nano Community", + "license": "MIT", + "dependencies": { + "@trashman/ed25519-blake2b": "0.0.6", + "inquirer": "9.2.23", + "node-fetch": "3.3.2", + "yargs": "17.7.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "packageManager": "yarn@3.2.1" +} diff --git a/cli/yarn.lock b/cli/yarn.lock new file mode 100644 index 00000000..93cafb66 --- /dev/null +++ b/cli/yarn.lock @@ -0,0 +1,1486 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + cacheKey: 8 + +"@inquirer/figures@npm:^1.0.3": + version: 1.0.3 + resolution: "@inquirer/figures@npm:1.0.3" + checksum: ca83d9e2a02ed5309b3df5642d2194fde24e6f89779339c63304f2570f36f3bc431236a93db7fa412765a06f01c765974b06b1ed8b9aed881be46f2cbb67f9c7 + languageName: node + linkType: hard + +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: ^5.1.2 + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: ^7.0.1 + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: ^8.1.0 + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 4a473b9b32a7d4d3cfb7a614226e555091ff0c5a29a1734c28c72a182c2f6699b26fc6b5c2131dfd841e86b185aea714c72201d7c98c2fba5f17709333a67aeb + languageName: node + linkType: hard + +"@ljharb/through@npm:^2.3.13": + version: 2.3.13 + resolution: "@ljharb/through@npm:2.3.13" + dependencies: + call-bind: ^1.0.7 + checksum: 0255464a0ec7901b08cff3e99370b87e66663f46249505959c0cb4f6121095d533bbb7c7cda338063d3e134cbdd721e2705bc18eac7611b4f9ead6e7935d13ba + languageName: node + linkType: hard + +"@npmcli/agent@npm:^2.0.0": + version: 2.2.2 + resolution: "@npmcli/agent@npm:2.2.2" + dependencies: + agent-base: ^7.1.0 + http-proxy-agent: ^7.0.0 + https-proxy-agent: ^7.0.1 + lru-cache: ^10.0.1 + socks-proxy-agent: ^8.0.3 + checksum: 67de7b88cc627a79743c88bab35e023e23daf13831a8aa4e15f998b92f5507b644d8ffc3788afc8e64423c612e0785a6a92b74782ce368f49a6746084b50d874 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^3.1.0": + version: 3.1.1 + resolution: "@npmcli/fs@npm:3.1.1" + dependencies: + semver: ^7.3.5 + checksum: d960cab4b93adcb31ce223bfb75c5714edbd55747342efb67dcc2f25e023d930a7af6ece3e75f2f459b6f38fc14d031c766f116cd124fdc937fd33112579e820 + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 6ad6a00fc4f2f2cfc6bff76fb1d88b8ee20bc0601e18ebb01b6d4be583733a860239a521a7fbca73b612e66705078809483549d2b18f370eb346c5155c8e4a0f + languageName: node + linkType: hard + +"@trashman/ed25519-blake2b@npm:0.0.6": + version: 0.0.6 + resolution: "@trashman/ed25519-blake2b@npm:0.0.6" + dependencies: + napi-macros: 2.0.0 + node-gyp: latest + node-gyp-build: 4.4.0 + checksum: 8ad5da41d1581f3502127727aa8f1c94259380a52de1b974567f0df785186f9b7355f2cb9ad45affc072e361aa8ce090d2c522e5383d4bb6fe4de7d9eacd603a + languageName: node + linkType: hard + +"abbrev@npm:^2.0.0": + version: 2.0.0 + resolution: "abbrev@npm:2.0.0" + checksum: 0e994ad2aa6575f94670d8a2149afe94465de9cedaaaac364e7fb43a40c3691c980ff74899f682f4ca58fa96b4cbd7421a015d3a6defe43a442117d7821a2f36 + languageName: node + linkType: hard + +"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": + version: 7.1.1 + resolution: "agent-base@npm:7.1.1" + dependencies: + debug: ^4.3.4 + checksum: 51c158769c5c051482f9ca2e6e1ec085ac72b5a418a9b31b4e82fe6c0a6699adb94c1c42d246699a587b3335215037091c79e0de512c516f73b6ea844202f037 + languageName: node + linkType: hard + +"aggregate-error@npm:^3.0.0": + version: 3.1.0 + resolution: "aggregate-error@npm:3.1.0" + dependencies: + clean-stack: ^2.0.0 + indent-string: ^4.0.0 + checksum: 1101a33f21baa27a2fa8e04b698271e64616b886795fd43c31068c07533c7b3facfcaf4e9e0cab3624bd88f729a592f1c901a1a229c9e490eafce411a8644b79 + languageName: node + linkType: hard + +"ansi-escapes@npm:^4.3.2": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: ^0.21.3 + checksum: 93111c42189c0a6bed9cdb4d7f2829548e943827ee8479c74d6e0b22ee127b2a21d3f8b5ca57723b8ef78ce011fbfc2784350eb2bde3ccfccf2f575fa8489815 + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 2aa4bb54caf2d622f1afdad09441695af2a83aa3fe8b8afa581d205e57ed4261c183c4d3877cee25794443fde5876417d859c108078ab788d6af7e4fe52eb66b + languageName: node + linkType: hard + +"ansi-regex@npm:^6.0.1": + version: 6.0.1 + resolution: "ansi-regex@npm:6.0.1" + checksum: 1ff8b7667cded1de4fa2c9ae283e979fc87036864317da86a2e546725f96406746411d0d85e87a2d12fa5abd715d90006de7fa4fa0477c92321ad3b4c7d4e169 + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: ^2.0.1 + checksum: 513b44c3b2105dd14cc42a19271e80f386466c4be574bccf60b627432f9198571ebf4ab1e4c3ba17347658f4ee1711c163d574248c0c1cdc2d5917a0ad582ec4 + languageName: node + linkType: hard + +"ansi-styles@npm:^6.1.0": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: ef940f2f0ced1a6347398da88a91da7930c33ecac3c77b72c5905f8b8fe402c52e6fde304ff5347f616e27a742da3f1dc76de98f6866c69251ad0b07a66776d9 + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 9706c088a283058a8a99e0bf91b0a2f75497f185980d9ffa8b304de1d9e58ebda7c72c07ebf01dadedaac5b2907b2c6f566f660d62bd336c3468e960403b9d65 + languageName: node + linkType: hard + +"base64-js@npm:^1.3.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 + languageName: node + linkType: hard + +"bl@npm:^4.1.0": + version: 4.1.0 + resolution: "bl@npm:4.1.0" + dependencies: + buffer: ^5.5.0 + inherits: ^2.0.4 + readable-stream: ^3.4.0 + checksum: 9e8521fa7e83aa9427c6f8ccdcba6e8167ef30cc9a22df26effcc5ab682ef91d2cbc23a239f945d099289e4bbcfae7a192e9c28c84c6202e710a0dfec3722662 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.1 + resolution: "brace-expansion@npm:2.0.1" + dependencies: + balanced-match: ^1.0.0 + checksum: a61e7cd2e8a8505e9f0036b3b6108ba5e926b4b55089eeb5550cd04a471fe216c96d4fe7e4c7f995c728c554ae20ddfc4244cad10aef255e72b62930afd233d1 + languageName: node + linkType: hard + +"buffer@npm:^5.5.0": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" + dependencies: + base64-js: ^1.3.1 + ieee754: ^1.1.13 + checksum: e2cf8429e1c4c7b8cbd30834ac09bd61da46ce35f5c22a78e6c2f04497d6d25541b16881e30a019c6fd3154150650ccee27a308eff3e26229d788bbdeb08ab84 + languageName: node + linkType: hard + +"cacache@npm:^18.0.0": + version: 18.0.3 + resolution: "cacache@npm:18.0.3" + dependencies: + "@npmcli/fs": ^3.1.0 + fs-minipass: ^3.0.0 + glob: ^10.2.2 + lru-cache: ^10.0.1 + minipass: ^7.0.3 + minipass-collect: ^2.0.1 + minipass-flush: ^1.0.5 + minipass-pipeline: ^1.2.4 + p-map: ^4.0.0 + ssri: ^10.0.0 + tar: ^6.1.11 + unique-filename: ^3.0.0 + checksum: b717fd9b36e9c3279bfde4545c3a8f6d5a539b084ee26a9504d48f83694beb724057d26e090b97540f9cc62bea18b9f6cf671c50e18fb7dac60eda9db691714f + languageName: node + linkType: hard + +"call-bind@npm:^1.0.7": + version: 1.0.7 + resolution: "call-bind@npm:1.0.7" + dependencies: + es-define-property: ^1.0.0 + es-errors: ^1.3.0 + function-bind: ^1.1.2 + get-intrinsic: ^1.2.4 + set-function-length: ^1.2.1 + checksum: 295c0c62b90dd6522e6db3b0ab1ce26bdf9e7404215bda13cfee25b626b5ff1a7761324d58d38b1ef1607fc65aca2d06e44d2e18d0dfc6c14b465b00d8660029 + languageName: node + linkType: hard + +"chalk@npm:^4.1.0": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: ^4.1.0 + supports-color: ^7.1.0 + checksum: fe75c9d5c76a7a98d45495b91b2172fa3b7a09e0cc9370e5c8feb1c567b85c4288e2b3fded7cfdd7359ac28d6b3844feb8b82b8686842e93d23c827c417e83fc + languageName: node + linkType: hard + +"chalk@npm:^5.3.0": + version: 5.3.0 + resolution: "chalk@npm:5.3.0" + checksum: 623922e077b7d1e9dedaea6f8b9e9352921f8ae3afe739132e0e00c275971bdd331268183b2628cf4ab1727c45ea1f28d7e24ac23ce1db1eb653c414ca8a5a80 + languageName: node + linkType: hard + +"chardet@npm:^0.7.0": + version: 0.7.0 + resolution: "chardet@npm:0.7.0" + checksum: 6fd5da1f5d18ff5712c1e0aed41da200d7c51c28f11b36ee3c7b483f3696dabc08927fc6b227735eb8f0e1215c9a8abd8154637f3eff8cada5959df7f58b024d + languageName: node + linkType: hard + +"chownr@npm:^2.0.0": + version: 2.0.0 + resolution: "chownr@npm:2.0.0" + checksum: c57cf9dd0791e2f18a5ee9c1a299ae6e801ff58fee96dc8bfd0dcb4738a6ce58dd252a3605b1c93c6418fe4f9d5093b28ffbf4d66648cb2a9c67eaef9679be2f + languageName: node + linkType: hard + +"clean-stack@npm:^2.0.0": + version: 2.2.0 + resolution: "clean-stack@npm:2.2.0" + checksum: 2ac8cd2b2f5ec986a3c743935ec85b07bc174d5421a5efc8017e1f146a1cf5f781ae962618f416352103b32c9cd7e203276e8c28241bbe946160cab16149fb68 + languageName: node + linkType: hard + +"cli-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "cli-cursor@npm:3.1.0" + dependencies: + restore-cursor: ^3.1.0 + checksum: 2692784c6cd2fd85cfdbd11f53aea73a463a6d64a77c3e098b2b4697a20443f430c220629e1ca3b195ea5ac4a97a74c2ee411f3807abf6df2b66211fec0c0a29 + languageName: node + linkType: hard + +"cli-spinners@npm:^2.5.0": + version: 2.9.2 + resolution: "cli-spinners@npm:2.9.2" + checksum: 1bd588289b28432e4676cb5d40505cfe3e53f2e4e10fbe05c8a710a154d6fe0ce7836844b00d6858f740f2ffe67cdc36e0fce9c7b6a8430e80e6388d5aa4956c + languageName: node + linkType: hard + +"cli-width@npm:^4.1.0": + version: 4.1.0 + resolution: "cli-width@npm:4.1.0" + checksum: 0a79cff2dbf89ef530bcd54c713703ba94461457b11e5634bd024c78796ed21401e32349c004995954e06f442d82609287e7aabf6a5f02c919a1cf3b9b6854ff + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: ^4.2.0 + strip-ansi: ^6.0.1 + wrap-ansi: ^7.0.0 + checksum: 79648b3b0045f2e285b76fb2e24e207c6db44323581e421c3acbd0e86454cba1b37aea976ab50195a49e7384b871e6dfb2247ad7dec53c02454ac6497394cb56 + languageName: node + linkType: hard + +"clone@npm:^1.0.2": + version: 1.0.4 + resolution: "clone@npm:1.0.4" + checksum: d06418b7335897209e77bdd430d04f882189582e67bd1f75a04565f3f07f5b3f119a9d670c943b6697d0afb100f03b866b3b8a1f91d4d02d72c4ecf2bb64b5dd + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: ~1.1.4 + checksum: 79e6bdb9fd479a205c71d89574fccfb22bd9053bd98c6c4d870d65c132e5e904e6034978e55b43d69fcaa7433af2016ee203ce76eeba9cfa554b373e7f7db336 + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.0": + version: 7.0.3 + resolution: "cross-spawn@npm:7.0.3" + dependencies: + path-key: ^3.1.0 + shebang-command: ^2.0.0 + which: ^2.0.1 + checksum: 671cc7c7288c3a8406f3c69a3ae2fc85555c04169e9d611def9a675635472614f1c0ed0ef80955d5b6d4e724f6ced67f0ad1bb006c2ea643488fcfef994d7f52 + languageName: node + linkType: hard + +"data-uri-to-buffer@npm:^4.0.0": + version: 4.0.1 + resolution: "data-uri-to-buffer@npm:4.0.1" + checksum: 0d0790b67ffec5302f204c2ccca4494f70b4e2d940fea3d36b09f0bb2b8539c2e86690429eb1f1dc4bcc9e4df0644193073e63d9ee48ac9fce79ec1506e4aa4c + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.3.4": + version: 4.3.5 + resolution: "debug@npm:4.3.5" + dependencies: + ms: 2.1.2 + peerDependenciesMeta: + supports-color: + optional: true + checksum: 7c002b51e256257f936dda09eb37167df952758c57badf6bf44bdc40b89a4bcb8e5a0a2e4c7b53f97c69e2970dd5272d33a757378a12c8f8e64ea7bf99e8e86e + languageName: node + linkType: hard + +"defaults@npm:^1.0.3": + version: 1.0.4 + resolution: "defaults@npm:1.0.4" + dependencies: + clone: ^1.0.2 + checksum: 3a88b7a587fc076b84e60affad8b85245c01f60f38fc1d259e7ac1d89eb9ce6abb19e27215de46b98568dd5bc48471730b327637e6f20b0f1bc85cf00440c80a + languageName: node + linkType: hard + +"define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: ^1.0.0 + es-errors: ^1.3.0 + gopd: ^1.0.1 + checksum: 8068ee6cab694d409ac25936eb861eea704b7763f7f342adbdfe337fc27c78d7ae0eff2364b2917b58c508d723c7a074326d068eef2e45c4edcd85cf94d0313b + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 7d00d7cd8e49b9afa762a813faac332dee781932d6f2c848dc348939c4253f1d4564341b7af1d041853bc3f32c2ef141b58e0a4d9862c17a7f08f68df1e0f1ed + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: d4c5c39d5a9868b5fa152f00cada8a936868fd3367f33f71be515ecee4c803132d11b31a6222b2571b1e5f7e13890156a94880345594d0ce7e3c9895f560f192 + languageName: node + linkType: hard + +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 8487182da74aabd810ac6d6f1994111dfc0e331b01271ae01ec1eb0ad7b5ecc2bbbbd2f053c05cb55a1ac30449527d819bbfbf0e3de1023db308cbcb47f86601 + languageName: node + linkType: hard + +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: ^0.6.2 + checksum: bb98632f8ffa823996e508ce6a58ffcf5856330fde839ae42c9e1f436cc3b5cc651d4aeae72222916545428e54fd0f6aa8862fd8d25bdbcc4589f1e3f3715e7f + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 65b5df55a8bab92229ab2b40dad3b387fad24613263d103a97f91c9fe43ceb21965cd3392b1ccb5d77088021e525c4e0481adb309625d0cb94ade1d1fb8dc17e + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 8b7b1be20d2de12d2255c0bc2ca638b7af5171142693299416e6a9339bd7d88fc8d7707d913d78e0993176005405a236b066b45666b27b797252c771156ace54 + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.0": + version: 1.0.0 + resolution: "es-define-property@npm:1.0.0" + dependencies: + get-intrinsic: ^1.2.4 + checksum: f66ece0a887b6dca71848fa71f70461357c0e4e7249696f81bad0a1f347eed7b31262af4a29f5d726dc026426f085483b6b90301855e647aa8e21936f07293c6 + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: ec1414527a0ccacd7f15f4a3bc66e215f04f595ba23ca75cdae0927af099b5ec865f9f4d33e9d7e86f512f252876ac77d4281a7871531a50678132429b1271b5 + languageName: node + linkType: hard + +"escalade@npm:^3.1.1": + version: 3.1.2 + resolution: "escalade@npm:3.1.2" + checksum: 1ec0977aa2772075493002bdbd549d595ff6e9393b1cb0d7d6fcaf78c750da0c158f180938365486f75cb69fba20294351caddfce1b46552a7b6c3cde52eaa02 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.1 + resolution: "exponential-backoff@npm:3.1.1" + checksum: 3d21519a4f8207c99f7457287291316306255a328770d320b401114ec8481986e4e467e854cb9914dd965e0a1ca810a23ccb559c642c88f4c7f55c55778a9b48 + languageName: node + linkType: hard + +"external-editor@npm:^3.1.0": + version: 3.1.0 + resolution: "external-editor@npm:3.1.0" + dependencies: + chardet: ^0.7.0 + iconv-lite: ^0.4.24 + tmp: ^0.0.33 + checksum: 1c2a616a73f1b3435ce04030261bed0e22d4737e14b090bb48e58865da92529c9f2b05b893de650738d55e692d071819b45e1669259b2b354bc3154d27a698c7 + languageName: node + linkType: hard + +"fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4": + version: 3.2.0 + resolution: "fetch-blob@npm:3.2.0" + dependencies: + node-domexception: ^1.0.0 + web-streams-polyfill: ^3.0.3 + checksum: f19bc28a2a0b9626e69fd7cf3a05798706db7f6c7548da657cbf5026a570945f5eeaedff52007ea35c8bcd3d237c58a20bf1543bc568ab2422411d762dd3d5bf + languageName: node + linkType: hard + +"foreground-child@npm:^3.1.0": + version: 3.2.1 + resolution: "foreground-child@npm:3.2.1" + dependencies: + cross-spawn: ^7.0.0 + signal-exit: ^4.0.1 + checksum: 3e2e844d6003c96d70affe8ae98d7eaaba269a868c14d997620c088340a8775cd5d2d9043e6ceebae1928d8d9a874911c4d664b9a267e8995945df20337aebc0 + languageName: node + linkType: hard + +"formdata-polyfill@npm:^4.0.10": + version: 4.0.10 + resolution: "formdata-polyfill@npm:4.0.10" + dependencies: + fetch-blob: ^3.1.2 + checksum: 82a34df292afadd82b43d4a740ce387bc08541e0a534358425193017bf9fb3567875dc5f69564984b1da979979b70703aa73dee715a17b6c229752ae736dd9db + languageName: node + linkType: hard + +"fs-minipass@npm:^2.0.0": + version: 2.1.0 + resolution: "fs-minipass@npm:2.1.0" + dependencies: + minipass: ^3.0.0 + checksum: 1b8d128dae2ac6cc94230cc5ead341ba3e0efaef82dab46a33d171c044caaa6ca001364178d42069b2809c35a1c3c35079a32107c770e9ffab3901b59af8c8b1 + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: ^7.0.3 + checksum: 8722a41109130851d979222d3ec88aabaceeaaf8f57b2a8f744ef8bd2d1ce95453b04a61daa0078822bc5cd21e008814f06fe6586f56fef511e71b8d2394d802 + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 2b0ff4ce708d99715ad14a6d1f894e2a83242e4a52ccfcefaee5e40050562e5f6dafc1adbb4ce2d4ab47279a45dc736ab91ea5042d843c3c092820dfe032efb1 + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9 + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.4": + version: 1.2.4 + resolution: "get-intrinsic@npm:1.2.4" + dependencies: + es-errors: ^1.3.0 + function-bind: ^1.1.2 + has-proto: ^1.0.1 + has-symbols: ^1.0.3 + hasown: ^2.0.0 + checksum: 414e3cdf2c203d1b9d7d33111df746a4512a1aa622770b361dadddf8ed0b5aeb26c560f49ca077e24bfafb0acb55ca908d1f709216ccba33ffc548ec8a79a951 + languageName: node + linkType: hard + +"glob@npm:^10.2.2, glob@npm:^10.3.10": + version: 10.4.1 + resolution: "glob@npm:10.4.1" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^3.1.2 + minimatch: ^9.0.4 + minipass: ^7.1.2 + path-scurry: ^1.11.1 + bin: + glob: dist/esm/bin.mjs + checksum: 5d33c686c80bf6877f4284adf99a8c3cbb2a6eccbc92342943fe5d4b42c01d78c1881f2223d950c92a938d0f857e12e37b86a8e5483ab2141822e053b67d0dde + languageName: node + linkType: hard + +"gopd@npm:^1.0.1": + version: 1.0.1 + resolution: "gopd@npm:1.0.1" + dependencies: + get-intrinsic: ^1.1.3 + checksum: a5ccfb8806e0917a94e0b3de2af2ea4979c1da920bc381667c260e00e7cafdbe844e2cb9c5bcfef4e5412e8bf73bab837285bc35c7ba73aaaf0134d4583393a6 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 261a1357037ead75e338156b1f9452c016a37dcd3283a972a30d9e4a87441ba372c8b81f818cd0fbcd9c0354b4ae7e18b9e1afa1971164aef6d18c2b6095a8ad + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: ^1.0.0 + checksum: fcbb246ea2838058be39887935231c6d5788babed499d0e9d0cc5737494c48aba4fe17ba1449e0d0fbbb1e36175442faa37f9c427ae357d6ccb1d895fbcd3de3 + languageName: node + linkType: hard + +"has-proto@npm:^1.0.1": + version: 1.0.3 + resolution: "has-proto@npm:1.0.3" + checksum: fe7c3d50b33f50f3933a04413ed1f69441d21d2d2944f81036276d30635cad9279f6b43bc8f32036c31ebdfcf6e731150f46c1907ad90c669ffe9b066c3ba5c4 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3": + version: 1.0.3 + resolution: "has-symbols@npm:1.0.3" + checksum: a054c40c631c0d5741a8285010a0777ea0c068f99ed43e5d6eb12972da223f8af553a455132fdb0801bdcfa0e0f443c0c03a68d8555aa529b3144b446c3f2410 + languageName: node + linkType: hard + +"hasown@npm:^2.0.0": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: ^1.1.2 + checksum: e8516f776a15149ca6c6ed2ae3110c417a00b62260e222590e54aa367cbcd6ed99122020b37b7fbdf05748df57b265e70095d7bf35a47660587619b15ffb93db + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: ^7.1.0 + debug: ^4.3.4 + checksum: 670858c8f8f3146db5889e1fa117630910101db601fff7d5a8aa637da0abedf68c899f03d3451cac2f83bcc4c3d2dabf339b3aa00ff8080571cceb02c3ce02f3 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1": + version: 7.0.4 + resolution: "https-proxy-agent@npm:7.0.4" + dependencies: + agent-base: ^7.0.2 + debug: 4 + checksum: daaab857a967a2519ddc724f91edbbd388d766ff141b9025b629f92b9408fc83cee8a27e11a907aede392938e9c398e240d643e178408a59e4073539cde8cfe9 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: ">= 2.1.2 < 3" + checksum: bd9f120f5a5b306f0bc0b9ae1edeb1577161503f5f8252a20f1a9e56ef8775c9959fd01c55f2d3a39d9a8abaf3e30c1abeb1895f367dcbbe0a8fd1c9ca01c4f6 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: ">= 2.1.2 < 3.0.0" + checksum: 3f60d47a5c8fc3313317edfd29a00a692cc87a19cac0159e2ce711d0ebc9019064108323b5e493625e25594f11c6236647d8e256fbe7a58f4a3b33b89e6d30bf + languageName: node + linkType: hard + +"ieee754@npm:^1.1.13": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 5144c0c9815e54ada181d80a0b810221a253562422e7c6c3a60b1901154184f49326ec239d618c416c1c5945a2e197107aee8d986a3dd836b53dffefd99b5e7e + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 7cae75c8cd9a50f57dadd77482359f659eaebac0319dd9368bcd1714f55e65badd6929ca58569da2b6494ef13fdd5598cd700b1eba23f8b79c5f19d195a3ecf7 + languageName: node + linkType: hard + +"indent-string@npm:^4.0.0": + version: 4.0.0 + resolution: "indent-string@npm:4.0.0" + checksum: 824cfb9929d031dabf059bebfe08cf3137365e112019086ed3dcff6a0a7b698cb80cf67ccccde0e25b9e2d7527aa6cc1fed1ac490c752162496caba3e6699612 + languageName: node + linkType: hard + +"inherits@npm:^2.0.3, inherits@npm:^2.0.4": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 + languageName: node + linkType: hard + +"inquirer@npm:9.2.23": + version: 9.2.23 + resolution: "inquirer@npm:9.2.23" + dependencies: + "@inquirer/figures": ^1.0.3 + "@ljharb/through": ^2.3.13 + ansi-escapes: ^4.3.2 + chalk: ^5.3.0 + cli-cursor: ^3.1.0 + cli-width: ^4.1.0 + external-editor: ^3.1.0 + lodash: ^4.17.21 + mute-stream: 1.0.0 + ora: ^5.4.1 + run-async: ^3.0.0 + rxjs: ^7.8.1 + string-width: ^4.2.3 + strip-ansi: ^6.0.1 + wrap-ansi: ^6.2.0 + checksum: d3d5c48b2c3e606b59bc0b825a0dc3805cd12b4068cf9bb17e5f05a43e512f28b280124f2b3e9e8f8845c1031fb92eca73be25e764c4522e02b48af08428117c + languageName: node + linkType: hard + +"ip-address@npm:^9.0.5": + version: 9.0.5 + resolution: "ip-address@npm:9.0.5" + dependencies: + jsbn: 1.1.0 + sprintf-js: ^1.1.3 + checksum: aa15f12cfd0ef5e38349744e3654bae649a34c3b10c77a674a167e99925d1549486c5b14730eebce9fea26f6db9d5e42097b00aa4f9f612e68c79121c71652dc + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 44a30c29457c7fb8f00297bce733f0a64cd22eca270f83e58c105e0d015e45c019491a4ab2faef91ab51d4738c670daff901c799f6a700e27f7314029e99e348 + languageName: node + linkType: hard + +"is-interactive@npm:^1.0.0": + version: 1.0.0 + resolution: "is-interactive@npm:1.0.0" + checksum: 824808776e2d468b2916cdd6c16acacebce060d844c35ca6d82267da692e92c3a16fdba624c50b54a63f38bdc4016055b6f443ce57d7147240de4f8cdabaf6f9 + languageName: node + linkType: hard + +"is-lambda@npm:^1.0.1": + version: 1.0.1 + resolution: "is-lambda@npm:1.0.1" + checksum: 93a32f01940220532e5948538699ad610d5924ac86093fcee83022252b363eb0cc99ba53ab084a04e4fb62bf7b5731f55496257a4c38adf87af9c4d352c71c35 + languageName: node + linkType: hard + +"is-unicode-supported@npm:^0.1.0": + version: 0.1.0 + resolution: "is-unicode-supported@npm:0.1.0" + checksum: a2aab86ee7712f5c2f999180daaba5f361bdad1efadc9610ff5b8ab5495b86e4f627839d085c6530363c6d6d4ecbde340fb8e54bdb83da4ba8e0865ed5513c52 + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 26bf6c5480dda5161c820c5b5c751ae1e766c587b1f951ea3fcfc973bafb7831ae5b54a31a69bd670220e42e99ec154475025a468eae58ea262f813fdc8d1c62 + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 7fe1931ee4e88eb5aa524cd3ceb8c882537bc3a81b02e438b240e47012eef49c86904d0f0e593ea7c3a9996d18d0f1f3be8d3eaa92333977b0c3a9d353d5563e + languageName: node + linkType: hard + +"jackspeak@npm:^3.1.2": + version: 3.4.0 + resolution: "jackspeak@npm:3.4.0" + dependencies: + "@isaacs/cliui": ^8.0.2 + "@pkgjs/parseargs": ^0.11.0 + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 350f6f311018bb175ffbe736b19c26ac0b134bb5a17a638169e89594eb0c24ab1c658ab3a2fda24ff63b3b19292e1a5ec19d2255bc526df704e8168d392bef85 + languageName: node + linkType: hard + +"jsbn@npm:1.1.0": + version: 1.1.0 + resolution: "jsbn@npm:1.1.0" + checksum: 944f924f2bd67ad533b3850eee47603eed0f6ae425fd1ee8c760f477e8c34a05f144c1bd4f5a5dd1963141dc79a2c55f89ccc5ab77d039e7077f3ad196b64965 + languageName: node + linkType: hard + +"lodash@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 + languageName: node + linkType: hard + +"log-symbols@npm:^4.1.0": + version: 4.1.0 + resolution: "log-symbols@npm:4.1.0" + dependencies: + chalk: ^4.1.0 + is-unicode-supported: ^0.1.0 + checksum: fce1497b3135a0198803f9f07464165e9eb83ed02ceb2273930a6f8a508951178d8cf4f0378e9d28300a2ed2bc49050995d2bd5f53ab716bb15ac84d58c6ef74 + languageName: node + linkType: hard + +"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": + version: 10.2.2 + resolution: "lru-cache@npm:10.2.2" + checksum: 98e8fc93691c546f719a76103ef2bee5a3ac823955c755a47641ec41f8c7fafa1baeaba466937cc1cbfa9cfd47e03536d10e2db3158a64ad91ff3a58a32c893e + languageName: node + linkType: hard + +"make-fetch-happen@npm:^13.0.0": + version: 13.0.1 + resolution: "make-fetch-happen@npm:13.0.1" + dependencies: + "@npmcli/agent": ^2.0.0 + cacache: ^18.0.0 + http-cache-semantics: ^4.1.1 + is-lambda: ^1.0.1 + minipass: ^7.0.2 + minipass-fetch: ^3.0.0 + minipass-flush: ^1.0.5 + minipass-pipeline: ^1.2.4 + negotiator: ^0.6.3 + proc-log: ^4.2.0 + promise-retry: ^2.0.1 + ssri: ^10.0.0 + checksum: 5c9fad695579b79488fa100da05777213dd9365222f85e4757630f8dd2a21a79ddd3206c78cfd6f9b37346819681782b67900ac847a57cf04190f52dda5343fd + languageName: node + linkType: hard + +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: d2421a3444848ce7f84bd49115ddacff29c15745db73f54041edc906c14b131a38d05298dae3081667627a59b2eb1ca4b436ff2e1b80f69679522410418b478a + languageName: node + linkType: hard + +"minimatch@npm:^9.0.4": + version: 9.0.4 + resolution: "minimatch@npm:9.0.4" + dependencies: + brace-expansion: ^2.0.1 + checksum: cf717f597ec3eed7dabc33153482a2e8d49f4fd3c26e58fd9c71a94c5029a0838728841b93f46bf1263b65a8010e2ee800d0dc9b004ab8ba8b6d1ec07cc115b5 + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: ^7.0.3 + checksum: b251bceea62090f67a6cced7a446a36f4cd61ee2d5cea9aee7fff79ba8030e416327a1c5aa2908dc22629d06214b46d88fdab8c51ac76bacbf5703851b5ad342 + languageName: node + linkType: hard + +"minipass-fetch@npm:^3.0.0": + version: 3.0.5 + resolution: "minipass-fetch@npm:3.0.5" + dependencies: + encoding: ^0.1.13 + minipass: ^7.0.3 + minipass-sized: ^1.0.3 + minizlib: ^2.1.2 + dependenciesMeta: + encoding: + optional: true + checksum: 8047d273236157aab27ab7cd8eab7ea79e6ecd63e8f80c3366ec076cb9a0fed550a6935bab51764369027c414647fd8256c2a20c5445fb250c483de43350de83 + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: ^3.0.0 + checksum: 56269a0b22bad756a08a94b1ffc36b7c9c5de0735a4dd1ab2b06c066d795cfd1f0ac44a0fcae13eece5589b908ecddc867f04c745c7009be0b566421ea0944cf + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: ^3.0.0 + checksum: b14240dac0d29823c3d5911c286069e36d0b81173d7bdf07a7e4a91ecdef92cdff4baaf31ea3746f1c61e0957f652e641223970870e2353593f382112257971b + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: ^3.0.0 + checksum: 79076749fcacf21b5d16dd596d32c3b6bf4d6e62abb43868fac21674078505c8b15eaca4e47ed844985a4514854f917d78f588fcd029693709417d8f98b2bd60 + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: ^4.0.0 + checksum: a30d083c8054cee83cdcdc97f97e4641a3f58ae743970457b1489ce38ee1167b3aaf7d815cd39ec7a99b9c40397fd4f686e83750e73e652b21cb516f6d845e48 + languageName: node + linkType: hard + +"minipass@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass@npm:5.0.0" + checksum: 425dab288738853fded43da3314a0b5c035844d6f3097a8e3b5b29b328da8f3c1af6fc70618b32c29ff906284cf6406b6841376f21caaadd0793c1d5a6a620ea + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 2bfd325b95c555f2b4d2814d49325691c7bee937d753814861b0b49d5edcda55cbbf22b6b6a60bb91eddac8668771f03c5ff647dcd9d0f798e9548b9cdc46ee3 + languageName: node + linkType: hard + +"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": + version: 2.1.2 + resolution: "minizlib@npm:2.1.2" + dependencies: + minipass: ^3.0.0 + yallist: ^4.0.0 + checksum: f1fdeac0b07cf8f30fcf12f4b586795b97be856edea22b5e9072707be51fc95d41487faec3f265b42973a304fe3a64acd91a44a3826a963e37b37bafde0212c3 + languageName: node + linkType: hard + +"mkdirp@npm:^1.0.3": + version: 1.0.4 + resolution: "mkdirp@npm:1.0.4" + bin: + mkdirp: bin/cmd.js + checksum: a96865108c6c3b1b8e1d5e9f11843de1e077e57737602de1b82030815f311be11f96f09cce59bd5b903d0b29834733e5313f9301e3ed6d6f6fba2eae0df4298f + languageName: node + linkType: hard + +"ms@npm:2.1.2": + version: 2.1.2 + resolution: "ms@npm:2.1.2" + checksum: 673cdb2c3133eb050c745908d8ce632ed2c02d85640e2edb3ace856a2266a813b30c613569bf3354fdf4ea7d1a1494add3bfa95e2713baa27d0c2c71fc44f58f + languageName: node + linkType: hard + +"mute-stream@npm:1.0.0": + version: 1.0.0 + resolution: "mute-stream@npm:1.0.0" + checksum: 36fc968b0e9c9c63029d4f9dc63911950a3bdf55c9a87f58d3a266289b67180201cade911e7699f8b2fa596b34c9db43dad37649e3f7fdd13c3bb9edb0017ee7 + languageName: node + linkType: hard + +"nano-community-cli@workspace:.": + version: 0.0.0-use.local + resolution: "nano-community-cli@workspace:." + dependencies: + "@trashman/ed25519-blake2b": 0.0.6 + inquirer: 9.2.23 + node-fetch: 3.3.2 + yargs: 17.7.2 + bin: + nano-community: ./index.mjs + languageName: unknown + linkType: soft + +"napi-macros@npm:2.0.0": + version: 2.0.0 + resolution: "napi-macros@npm:2.0.0" + checksum: 30384819386977c1f82034757014163fa60ab3c5a538094f778d38788bebb52534966279956f796a92ea771c7f8ae072b975df65de910d051ffbdc927f62320c + languageName: node + linkType: hard + +"negotiator@npm:^0.6.3": + version: 0.6.3 + resolution: "negotiator@npm:0.6.3" + checksum: b8ffeb1e262eff7968fc90a2b6767b04cfd9842582a9d0ece0af7049537266e7b2506dfb1d107a32f06dd849ab2aea834d5830f7f4d0e5cb7d36e1ae55d021d9 + languageName: node + linkType: hard + +"node-domexception@npm:^1.0.0": + version: 1.0.0 + resolution: "node-domexception@npm:1.0.0" + checksum: ee1d37dd2a4eb26a8a92cd6b64dfc29caec72bff5e1ed9aba80c294f57a31ba4895a60fd48347cf17dd6e766da0ae87d75657dfd1f384ebfa60462c2283f5c7f + languageName: node + linkType: hard + +"node-fetch@npm:3.3.2": + version: 3.3.2 + resolution: "node-fetch@npm:3.3.2" + dependencies: + data-uri-to-buffer: ^4.0.0 + fetch-blob: ^3.1.4 + formdata-polyfill: ^4.0.10 + checksum: 06a04095a2ddf05b0830a0d5302699704d59bda3102894ea64c7b9d4c865ecdff2d90fd042df7f5bc40337266961cb6183dcc808ea4f3000d024f422b462da92 + languageName: node + linkType: hard + +"node-gyp-build@npm:4.4.0": + version: 4.4.0 + resolution: "node-gyp-build@npm:4.4.0" + bin: + node-gyp-build: bin.js + node-gyp-build-optional: optional.js + node-gyp-build-test: build-test.js + checksum: 972a059f960253d254e0b23ce10f54c8982236fc0edcab85166d0b7f87443b2ce98391c877cfb2f6eeafcf03c538c5f4dd3e0bfff03828eb48634f58f4c64343 + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 10.1.0 + resolution: "node-gyp@npm:10.1.0" + dependencies: + env-paths: ^2.2.0 + exponential-backoff: ^3.1.1 + glob: ^10.3.10 + graceful-fs: ^4.2.6 + make-fetch-happen: ^13.0.0 + nopt: ^7.0.0 + proc-log: ^3.0.0 + semver: ^7.3.5 + tar: ^6.1.2 + which: ^4.0.0 + bin: + node-gyp: bin/node-gyp.js + checksum: 72e2ab4b23fc32007a763da94018f58069fc0694bf36115d49a2b195c8831e12cf5dd1e7a3718fa85c06969aedf8fc126722d3b672ec1cb27e06ed33caee3c60 + languageName: node + linkType: hard + +"nopt@npm:^7.0.0": + version: 7.2.1 + resolution: "nopt@npm:7.2.1" + dependencies: + abbrev: ^2.0.0 + bin: + nopt: bin/nopt.js + checksum: 6fa729cc77ce4162cfad8abbc9ba31d4a0ff6850c3af61d59b505653bef4781ec059f8890ecfe93ee8aa0c511093369cca88bfc998101616a2904e715bbbb7c9 + languageName: node + linkType: hard + +"onetime@npm:^5.1.0": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: ^2.1.0 + checksum: 2478859ef817fc5d4e9c2f9e5728512ddd1dbc9fb7829ad263765bb6d3b91ce699d6e2332eef6b7dff183c2f490bd3349f1666427eaba4469fba0ac38dfd0d34 + languageName: node + linkType: hard + +"ora@npm:^5.4.1": + version: 5.4.1 + resolution: "ora@npm:5.4.1" + dependencies: + bl: ^4.1.0 + chalk: ^4.1.0 + cli-cursor: ^3.1.0 + cli-spinners: ^2.5.0 + is-interactive: ^1.0.0 + is-unicode-supported: ^0.1.0 + log-symbols: ^4.1.0 + strip-ansi: ^6.0.0 + wcwidth: ^1.0.1 + checksum: 28d476ee6c1049d68368c0dc922e7225e3b5600c3ede88fade8052837f9ed342625fdaa84a6209302587c8ddd9b664f71f0759833cbdb3a4cf81344057e63c63 + languageName: node + linkType: hard + +"os-tmpdir@npm:~1.0.2": + version: 1.0.2 + resolution: "os-tmpdir@npm:1.0.2" + checksum: 5666560f7b9f10182548bf7013883265be33620b1c1b4a4d405c25be2636f970c5488ff3e6c48de75b55d02bde037249fe5dbfbb4c0fb7714953d56aed062e6d + languageName: node + linkType: hard + +"p-map@npm:^4.0.0": + version: 4.0.0 + resolution: "p-map@npm:4.0.0" + dependencies: + aggregate-error: ^3.0.0 + checksum: cb0ab21ec0f32ddffd31dfc250e3afa61e103ef43d957cc45497afe37513634589316de4eb88abdfd969fe6410c22c0b93ab24328833b8eb1ccc087fc0442a1c + languageName: node + linkType: hard + +"path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 55cd7a9dd4b343412a8386a743f9c746ef196e57c823d90ca3ab917f90ab9f13dd0ded27252ba49dbdfcab2b091d998bc446f6220cd3cea65db407502a740020 + languageName: node + linkType: hard + +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: ^10.2.0 + minipass: ^5.0.0 || ^6.0.2 || ^7.0.0 + checksum: 890d5abcd593a7912dcce7cf7c6bf7a0b5648e3dee6caf0712c126ca0a65c7f3d7b9d769072a4d1baf370f61ce493ab5b038d59988688e0c5f3f646ee3c69023 + languageName: node + linkType: hard + +"proc-log@npm:^3.0.0": + version: 3.0.0 + resolution: "proc-log@npm:3.0.0" + checksum: 02b64e1b3919e63df06f836b98d3af002b5cd92655cab18b5746e37374bfb73e03b84fe305454614b34c25b485cc687a9eebdccf0242cda8fda2475dd2c97e02 + languageName: node + linkType: hard + +"proc-log@npm:^4.2.0": + version: 4.2.0 + resolution: "proc-log@npm:4.2.0" + checksum: 98f6cd012d54b5334144c5255ecb941ee171744f45fca8b43b58ae5a0c1af07352475f481cadd9848e7f0250376ee584f6aa0951a856ff8f021bdfbff4eb33fc + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: ^2.0.2 + retry: ^0.12.0 + checksum: f96a3f6d90b92b568a26f71e966cbbc0f63ab85ea6ff6c81284dc869b41510e6cdef99b6b65f9030f0db422bf7c96652a3fff9f2e8fb4a0f069d8f4430359429 + languageName: node + linkType: hard + +"readable-stream@npm:^3.4.0": + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" + dependencies: + inherits: ^2.0.3 + string_decoder: ^1.1.1 + util-deprecate: ^1.0.1 + checksum: bdcbe6c22e846b6af075e32cf8f4751c2576238c5043169a1c221c92ee2878458a816a4ea33f4c67623c0b6827c8a400409bfb3cf0bf3381392d0b1dfb52ac8d + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: fb47e70bf0001fdeabdc0429d431863e9475e7e43ea5f94ad86503d918423c1543361cc5166d713eaa7029dd7a3d34775af04764bebff99ef413111a5af18c80 + languageName: node + linkType: hard + +"restore-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "restore-cursor@npm:3.1.0" + dependencies: + onetime: ^5.1.0 + signal-exit: ^3.0.2 + checksum: f877dd8741796b909f2a82454ec111afb84eb45890eb49ac947d87991379406b3b83ff9673a46012fca0d7844bb989f45cc5b788254cf1a39b6b5a9659de0630 + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 623bd7d2e5119467ba66202d733ec3c2e2e26568074923bc0585b6b99db14f357e79bdedb63cab56cec47491c4a0da7e6021a7465ca6dc4f481d3898fdd3158c + languageName: node + linkType: hard + +"run-async@npm:^3.0.0": + version: 3.0.0 + resolution: "run-async@npm:3.0.0" + checksum: 280c03d5a88603f48103fc6fd69f07fb0c392a1e0d319c34ec96a2516030e07ba06f79231a563c78698b882649c2fc1fda601bc84705f57d50efcd1fa506cfc0 + languageName: node + linkType: hard + +"rxjs@npm:^7.8.1": + version: 7.8.1 + resolution: "rxjs@npm:7.8.1" + dependencies: + tslib: ^2.1.0 + checksum: de4b53db1063e618ec2eca0f7965d9137cabe98cf6be9272efe6c86b47c17b987383df8574861bcced18ebd590764125a901d5506082be84a8b8e364bf05f119 + languageName: node + linkType: hard + +"safe-buffer@npm:~5.2.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: cab8f25ae6f1434abee8d80023d7e72b598cf1327164ddab31003c51215526801e40b66c5e65d658a0af1e9d6478cadcb4c745f4bd6751f97d8644786c0978b0 + languageName: node + linkType: hard + +"semver@npm:^7.3.5": + version: 7.6.2 + resolution: "semver@npm:7.6.2" + bin: + semver: bin/semver.js + checksum: 40f6a95101e8d854357a644da1b8dd9d93ce786d5c6a77227bc69dbb17bea83d0d1d1d7c4cd5920a6df909f48e8bd8a5909869535007f90278289f2451d0292d + languageName: node + linkType: hard + +"set-function-length@npm:^1.2.1": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: ^1.1.4 + es-errors: ^1.3.0 + function-bind: ^1.1.2 + get-intrinsic: ^1.2.4 + gopd: ^1.0.1 + has-property-descriptors: ^1.0.2 + checksum: a8248bdacdf84cb0fab4637774d9fb3c7a8e6089866d04c817583ff48e14149c87044ce683d7f50759a8c50fb87c7a7e173535b06169c87ef76f5fb276dfff72 + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: ^3.0.0 + checksum: 6b52fe87271c12968f6a054e60f6bde5f0f3d2db483a1e5c3e12d657c488a15474121a1d55cd958f6df026a54374ec38a4a963988c213b7570e1d51575cea7fa + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 1a2bcae50de99034fcd92ad4212d8e01eedf52c7ec7830eedcf886622804fe36884278f2be8be0ea5fde3fd1c23911643a4e0f726c8685b61871c8908af01222 + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.2": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 + languageName: node + linkType: hard + +"signal-exit@npm:^4.0.1": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 64c757b498cb8629ffa5f75485340594d2f8189e9b08700e69199069c8e3070fb3e255f7ab873c05dc0b3cec412aea7402e10a5990cb6a050bd33ba062a6c549 + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: b5167a7142c1da704c0e3af85c402002b597081dd9575031a90b4f229ca5678e9a36e8a374f1814c8156a725d17008ae3bde63b92f9cfd132526379e580bec8b + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.3 + resolution: "socks-proxy-agent@npm:8.0.3" + dependencies: + agent-base: ^7.1.1 + debug: ^4.3.4 + socks: ^2.7.1 + checksum: 8fab38821c327c190c28f1658087bc520eb065d55bc07b4a0fdf8d1e0e7ad5d115abbb22a95f94f944723ea969dd771ad6416b1e3cde9060c4c71f705c8b85c5 + languageName: node + linkType: hard + +"socks@npm:^2.7.1": + version: 2.8.3 + resolution: "socks@npm:2.8.3" + dependencies: + ip-address: ^9.0.5 + smart-buffer: ^4.2.0 + checksum: 7a6b7f6eedf7482b9e4597d9a20e09505824208006ea8f2c49b71657427f3c137ca2ae662089baa73e1971c62322d535d9d0cf1c9235cf6f55e315c18203eadd + languageName: node + linkType: hard + +"sprintf-js@npm:^1.1.3": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: a3fdac7b49643875b70864a9d9b469d87a40dfeaf5d34d9d0c5b1cda5fd7d065531fcb43c76357d62254c57184a7b151954156563a4d6a747015cfb41021cad0 + languageName: node + linkType: hard + +"ssri@npm:^10.0.0": + version: 10.0.6 + resolution: "ssri@npm:10.0.6" + dependencies: + minipass: ^7.0.3 + checksum: 4603d53a05bcd44188747d38f1cc43833b9951b5a1ee43ba50535bdfc5fe4a0897472dbe69837570a5417c3c073377ef4f8c1a272683b401857f72738ee57299 + languageName: node + linkType: hard + +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: ^8.0.0 + is-fullwidth-code-point: ^3.0.0 + strip-ansi: ^6.0.1 + checksum: e52c10dc3fbfcd6c3a15f159f54a90024241d0f149cf8aed2982a2d801d2e64df0bf1dc351cf8e95c3319323f9f220c16e740b06faecd53e2462df1d2b5443fb + languageName: node + linkType: hard + +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: ^0.2.0 + emoji-regex: ^9.2.2 + strip-ansi: ^7.0.1 + checksum: 7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193 + languageName: node + linkType: hard + +"string_decoder@npm:^1.1.1": + version: 1.3.0 + resolution: "string_decoder@npm:1.3.0" + dependencies: + safe-buffer: ~5.2.0 + checksum: 8417646695a66e73aefc4420eb3b84cc9ffd89572861fe004e6aeb13c7bc00e2f616247505d2dbbef24247c372f70268f594af7126f43548565c68c117bdeb56 + languageName: node + linkType: hard + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: ^5.0.1 + checksum: f3cd25890aef3ba6e1a74e20896c21a46f482e93df4a06567cebf2b57edabb15133f1f94e57434e0a958d61186087b1008e89c94875d019910a213181a14fc8c + languageName: node + linkType: hard + +"strip-ansi@npm:^7.0.1": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" + dependencies: + ansi-regex: ^6.0.1 + checksum: 859c73fcf27869c22a4e4d8c6acfe690064659e84bef9458aa6d13719d09ca88dcfd40cbf31fd0be63518ea1a643fe070b4827d353e09533a5b0b9fd4553d64d + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: ^4.0.0 + checksum: 3dda818de06ebbe5b9653e07842d9479f3555ebc77e9a0280caf5a14fb877ffee9ed57007c3b78f5a6324b8dbeec648d9e97a24e2ed9fdb81ddc69ea07100f4a + languageName: node + linkType: hard + +"tar@npm:^6.1.11, tar@npm:^6.1.2": + version: 6.2.1 + resolution: "tar@npm:6.2.1" + dependencies: + chownr: ^2.0.0 + fs-minipass: ^2.0.0 + minipass: ^5.0.0 + minizlib: ^2.1.1 + mkdirp: ^1.0.3 + yallist: ^4.0.0 + checksum: f1322768c9741a25356c11373bce918483f40fa9a25c69c59410c8a1247632487edef5fe76c5f12ac51a6356d2f1829e96d2bc34098668a2fc34d76050ac2b6c + languageName: node + linkType: hard + +"tmp@npm:^0.0.33": + version: 0.0.33 + resolution: "tmp@npm:0.0.33" + dependencies: + os-tmpdir: ~1.0.2 + checksum: 902d7aceb74453ea02abbf58c203f4a8fc1cead89b60b31e354f74ed5b3fb09ea817f94fb310f884a5d16987dd9fa5a735412a7c2dd088dd3d415aa819ae3a28 + languageName: node + linkType: hard + +"tslib@npm:^2.1.0": + version: 2.6.3 + resolution: "tslib@npm:2.6.3" + checksum: 74fce0e100f1ebd95b8995fbbd0e6c91bdd8f4c35c00d4da62e285a3363aaa534de40a80db30ecfd388ed7c313c42d930ee0eaf108e8114214b180eec3dbe6f5 + languageName: node + linkType: hard + +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: e6b32a3b3877f04339bae01c193b273c62ba7bfc9e325b8703c4ee1b32dc8fe4ef5dfa54bf78265e069f7667d058e360ae0f37be5af9f153b22382cd55a9afe0 + languageName: node + linkType: hard + +"unique-filename@npm:^3.0.0": + version: 3.0.0 + resolution: "unique-filename@npm:3.0.0" + dependencies: + unique-slug: ^4.0.0 + checksum: 8e2f59b356cb2e54aab14ff98a51ac6c45781d15ceaab6d4f1c2228b780193dc70fae4463ce9e1df4479cb9d3304d7c2043a3fb905bdeca71cc7e8ce27e063df + languageName: node + linkType: hard + +"unique-slug@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-slug@npm:4.0.0" + dependencies: + imurmurhash: ^0.1.4 + checksum: 0884b58365af59f89739e6f71e3feacb5b1b41f2df2d842d0757933620e6de08eff347d27e9d499b43c40476cbaf7988638d3acb2ffbcb9d35fd035591adfd15 + languageName: node + linkType: hard + +"util-deprecate@npm:^1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 474acf1146cb2701fe3b074892217553dfcf9a031280919ba1b8d651a068c9b15d863b7303cb15bd00a862b498e6cf4ad7b4a08fb134edd5a6f7641681cb54a2 + languageName: node + linkType: hard + +"wcwidth@npm:^1.0.1": + version: 1.0.1 + resolution: "wcwidth@npm:1.0.1" + dependencies: + defaults: ^1.0.3 + checksum: 814e9d1ddcc9798f7377ffa448a5a3892232b9275ebb30a41b529607691c0491de47cba426e917a4d08ded3ee7e9ba2f3fe32e62ee3cd9c7d3bafb7754bd553c + languageName: node + linkType: hard + +"web-streams-polyfill@npm:^3.0.3": + version: 3.3.3 + resolution: "web-streams-polyfill@npm:3.3.3" + checksum: 21ab5ea08a730a2ef8023736afe16713b4f2023ec1c7085c16c8e293ee17ed085dff63a0ad8722da30c99c4ccbd4ccd1b2e79c861829f7ef2963d7de7004c2cb + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: ^2.0.0 + bin: + node-which: ./bin/node-which + checksum: 1a5c563d3c1b52d5f893c8b61afe11abc3bab4afac492e8da5bde69d550de701cf9806235f20a47b5c8fa8a1d6a9135841de2596535e998027a54589000e66d1 + languageName: node + linkType: hard + +"which@npm:^4.0.0": + version: 4.0.0 + resolution: "which@npm:4.0.0" + dependencies: + isexe: ^3.1.1 + bin: + node-which: bin/which.js + checksum: f17e84c042592c21e23c8195108cff18c64050b9efb8459589116999ea9da6dd1509e6a1bac3aeebefd137be00fabbb61b5c2bc0aa0f8526f32b58ee2f545651 + languageName: node + linkType: hard + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b + languageName: node + linkType: hard + +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: 6cd96a410161ff617b63581a08376f0cb9162375adeb7956e10c8cd397821f7eb2a6de24eb22a0b28401300bf228c86e50617cd568209b5f6775b93c97d2fe3a + languageName: node + linkType: hard + +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: ^6.1.0 + string-width: ^5.0.1 + strip-ansi: ^7.0.1 + checksum: 371733296dc2d616900ce15a0049dca0ef67597d6394c57347ba334393599e800bab03c41d4d45221b6bc967b8c453ec3ae4749eff3894202d16800fdfe0e238 + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 54f0fb95621ee60898a38c572c515659e51cc9d9f787fb109cef6fde4befbe1c4602dc999d30110feee37456ad0f1660fa2edcfde6a9a740f86a290999550d30 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 343617202af32df2a15a3be36a5a8c0c8545208f3d3dfbc6bb7c3e3b7e8c6f8e7485432e4f3b88da3031a6e20afa7c711eded32ddfb122896ac5d914e75848d5 + languageName: node + linkType: hard + +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c + languageName: node + linkType: hard + +"yargs@npm:17.7.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: ^8.0.1 + escalade: ^3.1.1 + get-caller-file: ^2.0.5 + require-directory: ^2.1.1 + string-width: ^4.2.3 + y18n: ^5.0.5 + yargs-parser: ^21.1.1 + checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a + languageName: node + linkType: hard diff --git a/common/binary-to-hex.mjs b/common/binary-to-hex.mjs new file mode 100644 index 00000000..f2e376e6 --- /dev/null +++ b/common/binary-to-hex.mjs @@ -0,0 +1,5 @@ +export default function binary_to_hex(buf) { + return Array.prototype.map + .call(new Uint8Array(buf), (x) => ('00' + x.toString(16)).slice(-2)) + .join('') +} diff --git a/common/decode-nano-address.mjs b/common/decode-nano-address.mjs new file mode 100644 index 00000000..cbe7e1ac --- /dev/null +++ b/common/decode-nano-address.mjs @@ -0,0 +1,13 @@ +import { decode_nano_base32 } from './nano-base-32.mjs' +import binary_to_hex from './binary-to-hex.mjs' + +export default function decode_nano_address({ address }) { + const cleaned_address = address.replace('nano_', '').replace('xrb_', '') + const decoded = decode_nano_base32(cleaned_address) + const public_key = binary_to_hex(decoded.subarray(0, 32)) + const checksum = binary_to_hex(decoded.subarray(32, 32 + 5)) + return { + public_key, + checksum + } +} diff --git a/common/encode-nano-address.mjs b/common/encode-nano-address.mjs new file mode 100644 index 00000000..1d6dbb0a --- /dev/null +++ b/common/encode-nano-address.mjs @@ -0,0 +1,13 @@ +import ed25519 from '@trashman/ed25519-blake2b' + +import { encode_nano_base32 } from './nano-base-32.mjs' + +export default function encode_nano_address({ + public_key_buf, + prefix = 'nano_' +}) { + const encoded_public_key = encode_nano_base32(public_key_buf) + const checksum = ed25519.hash(public_key_buf, 5).reverse() + const encoded_checksum = encode_nano_base32(checksum) + return prefix + encoded_public_key + encoded_checksum +} diff --git a/common/index.mjs b/common/index.mjs index ee2b771c..f5f714f8 100644 --- a/common/index.mjs +++ b/common/index.mjs @@ -5,6 +5,7 @@ import path, { dirname } from 'path' import config from '#config' import { BURN_ACCOUNT } from '#constants' import request from './request.mjs' +import { encode_nano_base32, decode_nano_base32 } from './nano-base-32.mjs' export * as cloudflare from './cloudflare.mjs' export { request } @@ -16,6 +17,11 @@ export { default as sign_nano_community_revoke_key } from './sign-nano-community export { default as verify_nano_community_revoke_key_signature } from './verify-nano-community-revoke-key-signature.mjs' export { default as sign_nano_community_link_key } from './sign-nano-community-link-key.mjs' export { default as verify_nano_community_link_key_signature } from './verify-nano-community-link-key-signature.mjs' +export { default as encode_nano_address } from './encode-nano-address.mjs' +export { default as decode_nano_address } from './decode-nano-address.mjs' +export { default as binary_to_hex } from './binary-to-hex.mjs' +export { default as is_nano_address_valid } from './is-nano-address-valid.mjs' +export { encode_nano_base32, decode_nano_base32 } const POST = (data) => ({ method: 'POST', diff --git a/common/is-nano-address-valid.mjs b/common/is-nano-address-valid.mjs new file mode 100644 index 00000000..d6b8f3be --- /dev/null +++ b/common/is-nano-address-valid.mjs @@ -0,0 +1,32 @@ +import ed25519 from '@trashman/ed25519-blake2b' + +import decode_nano_address from './decode-nano-address.mjs' + +const NANO_ADDRESS_RE = /^(xrb_|nano_)[13][13-9a-km-uw-z]{59}$/ + +const compare_arrays = (a, b) => { + if (a.length !== b.length) { + return false + } + + return a.every((byte, index) => byte === b[index]) +} + +export default function is_nano_address_valid(address) { + if (typeof address !== 'string') { + return false + } + + if (!NANO_ADDRESS_RE.test(address)) { + return false + } + + const { public_key, checksum } = decode_nano_address({ address }) + + const public_key_bytes = Buffer.from(public_key, 'hex') + const checksum_bytes = Buffer.from(checksum, 'hex') + const computed_checksum_bytes = ed25519.hash(public_key_bytes, 5).reverse() + const valid = compare_arrays(checksum_bytes, computed_checksum_bytes) + + return valid +} diff --git a/common/nano-base-32.mjs b/common/nano-base-32.mjs new file mode 100644 index 00000000..779cbf78 --- /dev/null +++ b/common/nano-base-32.mjs @@ -0,0 +1,103 @@ +/* +MIT License + +Copyright (c) 2018 Gray Olson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +const alphabet = '13456789abcdefghijkmnopqrstuwxyz' + +/** + * Encode provided Uint8Array using the Nano-specific Base-32 implementeation. + * @param view Input buffer formatted as a Uint8Array + * @returns + * @hidden + */ +export function encode_nano_base32(view) { + const length = view.length + const leftover = (length * 8) % 5 + const offset = leftover === 0 ? 0 : 5 - leftover + + let value = 0 + let output = '' + let bits = 0 + + for (let i = 0; i < length; i++) { + value = (value << 8) | view[i] + bits += 8 + + while (bits >= 5) { + output += alphabet[(value >>> (bits + offset - 5)) & 31] + bits -= 5 + } + } + + if (bits > 0) { + output += alphabet[(value << (5 - (bits + offset))) & 31] + } + + return output +} + +function readChar(char) { + const idx = alphabet.indexOf(char) + + if (idx === -1) { + throw new Error(`Invalid character found: ${char}`) + } + + return idx +} + +/** + * Decodes a Nano-implementation Base32 encoded string into a Uint8Array + * @param input A Nano-Base32 encoded string + * @returns + * @hidden + */ +export function decode_nano_base32(input) { + const length = input.length + const leftover = (length * 5) % 8 + const offset = leftover === 0 ? 0 : 8 - leftover + + let bits = 0 + let value = 0 + + let index = 0 + let output = new Uint8Array(Math.ceil((length * 5) / 8)) + + for (let i = 0; i < length; i++) { + value = (value << 5) | readChar(input[i]) + bits += 5 + + if (bits >= 8) { + output[index++] = (value >>> (bits + offset - 8)) & 255 + bits -= 8 + } + } + if (bits > 0) { + output[index++] = (value << (bits + offset - 8)) & 255 + } + + if (leftover !== 0) { + output = output.slice(1) + } + return output +} diff --git a/common/sign-nano-community-revoke-key.mjs b/common/sign-nano-community-revoke-key.mjs index 41614d4f..bc96d5c1 100644 --- a/common/sign-nano-community-revoke-key.mjs +++ b/common/sign-nano-community-revoke-key.mjs @@ -2,33 +2,24 @@ import ed25519 from '@trashman/ed25519-blake2b' export default function sign_nano_community_revoke_key({ linked_public_key, - nano_account, - nano_account_private_key, - nano_account_public_key + either_private_key, + either_public_key }) { if (!linked_public_key) { throw new Error('linked_public_key is required') } - if (!nano_account) { - throw new Error('nano_account is required') + if (!either_private_key) { + throw new Error('either_private_key is required') } - if (!nano_account_private_key) { - throw new Error('nano_account_private_key is required') + if (!either_public_key) { + throw new Error('either_public_key is required') } - if (!nano_account_public_key) { - throw new Error('nano_account_public_key is required') - } - - const data = Buffer.from(['REVOKE', nano_account, linked_public_key]) + const data = Buffer.from(['REVOKE', linked_public_key]) const message_hash = ed25519.hash(data) - return ed25519.sign( - message_hash, - nano_account_private_key, - nano_account_public_key - ) + return ed25519.sign(message_hash, either_private_key, either_public_key) } diff --git a/common/verify-nano-community-revoke-key-signature.mjs b/common/verify-nano-community-revoke-key-signature.mjs index 780cd743..30784bf8 100644 --- a/common/verify-nano-community-revoke-key-signature.mjs +++ b/common/verify-nano-community-revoke-key-signature.mjs @@ -2,28 +2,23 @@ import ed25519 from '@trashman/ed25519-blake2b' export default function verify_nano_community_revoke_key_signature({ linked_public_key, - nano_account, - nano_account_public_key, + either_public_key, signature }) { if (!linked_public_key) { throw new Error('linked_public_key is required') } - if (!nano_account) { - throw new Error('nano_account is required') - } - - if (!nano_account_public_key) { - throw new Error('nano_account_public_key is required') + if (!either_public_key) { + throw new Error('either_public_key is required') } if (!signature) { throw new Error('signature is required') } - const data = Buffer.from(['REVOKE', nano_account, linked_public_key]) + const data = Buffer.from(['REVOKE', linked_public_key]) const message_hash = ed25519.hash(data) - return ed25519.verify(signature, message_hash, nano_account_public_key) + return ed25519.verify(signature, message_hash, either_public_key) } diff --git a/db/schema.sql b/db/schema.sql index 87eaec06..52a5594c 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -19,6 +19,21 @@ CREATE TABLE `accounts` ( -- -------------------------------------------------------- -- +-- Table structure for table `accounts_changelog` +-- + +DROP TABLE IF EXISTS `accounts_changelog`; + +CREATE TABLE `accounts_changelog` ( + `account` char(65) NOT NULL, + `column` varchar(65) NOT NULL, + `previous_value` varchar(1000) CHARACTER SET utf8mb4 DEFAULT '', + `new_value` varchar(1000) CHARACTER SET utf8mb4 DEFAULT '', + `timestamp` int(11) NOT NULL, + UNIQUE `change` (`account`, `column`, `previous_value`(60), `new_value`(60)) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- ---------------------------------------------------------- -- Table structure for table `account_keys` -- @@ -225,7 +240,7 @@ CREATE TABLE `nano_community_messages` ( `entry_clock` int(10) unsigned DEFAULT NULL, `chain_clock` int(10) unsigned DEFAULT NULL, `public_key` varchar(64) NOT NULL, - `operation` varchar(10) NOT NULL, + `operation` varchar(50) NOT NULL, `content` text CHARACTER SET utf8mb4 DEFAULT NULL, `tags` text CHARACTER SET utf8mb4 DEFAULT NULL, `references` text CHARACTER SET utf8mb4 DEFAULT NULL, @@ -295,6 +310,7 @@ CREATE TABLE `representatives_meta_index` ( `bandwidth_description` varchar(255) CHARACTER SET utf8 DEFAULT NULL, `ram` int(3) DEFAULT NULL, `ram_description` varchar(255) DEFAULT NULL, + `donation_address` char(65) DEFAULT NULL, `description` varchar(1000) CHARACTER SET utf8mb4 DEFAULT NULL, `dedicated` tinyint(1) DEFAULT NULL, diff --git a/docs/cli.md b/docs/cli.md new file mode 100644 index 00000000..3ab4e492 --- /dev/null +++ b/docs/cli.md @@ -0,0 +1,135 @@ +--- +title: Nano.Community CLI +description: Documentation for the Nano.Community CLI +tags: nano, xno, cli, nano-community, alias, representative, metadata, signing key +--- + +# Nano.Community CLI + +## Installation + +The Nano.Community CLI is available as a global npm package. You'll need to have [Node.js installed](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) to use it. + +```bash +npm install -g nano-community +``` + +It is also available as a yarn global package. + +```bash +yarn global add nano-community +``` + +### Setting Environment Variables (optional) + +The CLI will prompt you for the private key if it is not already set as an environment variable. Using the CLI in this way is simple and secure, as the private key will neither be stored in command history nor saved to a file. + +If you prefer setting the environment variable, you have three options: + +- **For a single command:** Use this method for one-time CLI calls. Note that using this in the terminal may store the private key in your command history, so it's better suited for programmatic use. +- **For a single session:** This temporarily sets the private key for the duration of the terminal session. Be aware that the key may still be recorded in your command history. +- **For all sessions:** This method saves the private key in a file within your home directory, ensuring it's available for all sessions. + +Choose the method that best suits your security and convenience needs. + +#### Setting the environment variable for a single command + +You can set the environment variable for a single command by passing it as an argument. + +```bash +NC_CLI_NANO_PRIVATE_KEY='your_private_key_here' nano-community update-rep-meta +``` + +#### Setting the environment variable for a single session + +**Linux/Mac:** + +```bash +export NC_CLI_NANO_PRIVATE_KEY='your_private_key_here' +``` + +**Windows:** + +```cmd +set NC_CLI_NANO_PRIVATE_KEY=your_private_key_here +``` + +This will persist for the duration of the current session in the terminal. You can now run commands without having to set the environment variable for each command. + +#### Setting the environment variable for all sessions + +You can persist the environment variable for all sessions by adding it to your `.bashrc`, `.zshrc`, or `.bash_profile` file in your home directory. + +## Usage + +### Setting up a signing key (optional) + +The purpose of a signing key is to sign messages to manage metadata related to a nano account/representative or block while minimizing the exposure of the account private key. This is optional but recommended. + +```bash +nano-community add-signing-key +``` + +This will generate a new signing key and output the public and private keys in the console. Take care to securely store the private key. + +This new signing key can now be used in place of your account key. Make sure to replace the `NC_CLI_NANO_PRIVATE_KEY` environment variable with the newly generated private key of the signing key. + +#### Revoking a signing key + +To revoke a signing key, use the `revoke-signing-key` command. The signing key can be revoked by either the signing key or the account key. + +```bash +nano-community revoke-signing-key +``` + +### Updating Nano Representative Metadata + +To update the metadata for a Nano representative use the `update-rep-meta` command. + +1. Run the command: + ```bash + nano-community update-rep-meta + ``` +2. The CLI will prompt you for the private key if one is not set in an environment variable. +3. You will be prompted to enter various metadata fields such as alias, description, donation address, etc. Fill these out as required. +4. Review the entered data when prompted, and confirm to proceed. +5. The CLI will sign and send the metadata update to the nano.community API, confirming the request in the console output. + +Supported metadata fields: + +- alias +- description +- donation_address +- cpu_model +- cpu_cores +- ram +- reddit +- twitter +- discord +- github +- email +- website + +### Updating Nano Account Metadata + +You can set a public alias for a nano account using the `update-account-meta` command. + +```bash +nano-community update-account-meta +``` + +Supported metadata fields: + +- alias + +### Updating Nano Block Metadata + +You can set a public message for a nano block using the `update-block-meta` command. + +```bash +nano-community update-block-meta +``` + +Supported metadata fields: + +- note diff --git a/libs-server/index.mjs b/libs-server/index.mjs new file mode 100644 index 00000000..b90b8b35 --- /dev/null +++ b/libs-server/index.mjs @@ -0,0 +1,3 @@ +export { default as update_account } from './update-account.mjs' +export { default as update_representative_meta } from './update-representative-meta.mjs' +export { default as process_community_message } from './process-community-message.mjs' diff --git a/libs-server/process-community-message.mjs b/libs-server/process-community-message.mjs new file mode 100644 index 00000000..883f6832 --- /dev/null +++ b/libs-server/process-community-message.mjs @@ -0,0 +1,116 @@ +import debug from 'debug' + +import update_account from './update-account.mjs' +import update_representative_meta from './update-representative-meta.mjs' + +const log = debug('process-community-message') + +const process_set_representative_meta = async ({ + message_content, + message_account +}) => { + if (!message_content) { + log( + `No message content found for SET_REPRESENTATIVE_META message from ${message_account}` + ) + return + } + + const { alias } = message_content + if (alias) { + await update_account({ + account_address: message_account, + update: { alias } + }) + } + + const { + cpu_cores, + description, + donation_address, + cpu_model, + ram, + reddit, + twitter, + discord, + github, + email, + website + } = message_content + + await update_representative_meta({ + representative_account_address: message_account, + update: { + cpu_cores, + description, + donation_address, + cpu_model, + ram, + reddit, + twitter, + discord, + github, + email, + website + } + }) +} + +const process_set_account_meta = async ({ + message_content, + message_account +}) => { + if (!message_content) { + log( + `No message content found for SET_ACCOUNT_META message from ${message_account}` + ) + return + } + + const { alias } = message_content + if (alias) { + await update_account({ + account_address: message_account, + update: { alias } + }) + } +} + +export default async function process_community_message({ + message, + message_account +}) { + let message_content + try { + message_content = JSON.parse(message.content) + } catch (error) { + log(`Error parsing message content: ${error}`) + return + } + + if (!message_content) { + log( + `No message content found for ${message.operation} message from ${message_account}` + ) + return + } + + switch (message.operation) { + case 'SET_ACCOUNT_META': + return process_set_account_meta({ + message, + message_content, + message_account + }) + + case 'SET_REPRESENTATIVE_META': + return process_set_representative_meta({ + message, + message_content, + message_account + }) + + default: + log(`Unsupported message operation: ${message.operation}`) + } +} diff --git a/libs-server/update-account.mjs b/libs-server/update-account.mjs new file mode 100644 index 00000000..ae6ba215 --- /dev/null +++ b/libs-server/update-account.mjs @@ -0,0 +1,108 @@ +import diff from 'deep-diff' +import debug from 'debug' + +import db from '#db' + +const log = debug('update-account') + +const nullable_columns = [] +const excluded_columns = [] +const editable_columns = ['alias', 'monitor_url', 'watt_hour'] + +export default async function update_account({ + account_row, + account_address, + update +}) { + if (!update) { + return 0 + } + + if ( + !account_row && + (typeof account_address === 'string' || account_address instanceof String) + ) { + account_row = await db('accounts') + .where({ account: account_address }) + .first() + + // If account_row is still not found, create a new one + if (!account_row) { + await db('accounts').insert({ + account: account_address + }) + account_row = await db('accounts') + .where({ account: account_address }) + .first() + } + } + + if (!account_row) { + return 0 + } + + if (!account_row.account) { + throw new Error('Account is missing account address') + } + + const formatted_update = { + ...update, + account: account_row.account + } + + // TODO format & validate params + + const differences = diff(account_row, formatted_update) + + const edits = differences.filter((d) => d.kind === 'E') + if (!edits.length) { + return 0 + } + + let changes_count = 0 + for (const edit of edits) { + const prop = edit.path[0] + + if (!editable_columns.includes(prop)) { + continue + } + + const is_null = !edit.rhs + const is_nullable = nullable_columns.includes(prop) + if (is_null && !is_nullable) { + continue + } + + if (excluded_columns.includes(prop)) { + log(`not allowed to edit ${prop}`) + continue + } + + log( + `updating account: ${account_row.account}, Column: ${prop}, Value: ${edit.lhs} => ${edit.rhs}` + ) + + const has_existing_value = edit.lhs + if (has_existing_value) { + await db('accounts_changelog').insert({ + account: account_row.account, + column: prop, + previous_value: edit.lhs, + new_value: edit.rhs, + timestamp: Math.floor(Date.now() / 1000) + }) + } + + await db('accounts') + .update({ + [prop]: edit.rhs + }) + .where({ + account: account_row.account + }) + + changes_count += 1 + } + + return changes_count +} diff --git a/libs-server/update-representative-meta.mjs b/libs-server/update-representative-meta.mjs new file mode 100644 index 00000000..b313be41 --- /dev/null +++ b/libs-server/update-representative-meta.mjs @@ -0,0 +1,121 @@ +import diff from 'deep-diff' +import debug from 'debug' + +import db from '#db' + +const log = debug('update-representative-meta') + +const nullable_columns = [] +const excluded_columns = [] +const editable_columns = [ + 'alias', + 'description', + 'donation_address', + 'cpu_model', + 'cpu_cores', + 'ram', + 'reddit', + 'twitter', + 'discord', + 'github', + 'email', + 'website' +] + +export default async function update_representative_meta({ + representative_row, + representative_account_address, + update +}) { + if (!update) { + return 0 + } + + if ( + !representative_row && + (typeof representative_account_address === 'string' || + representative_account_address instanceof String) + ) { + representative_row = await db('representatives_meta_index') + .where({ account: representative_account_address }) + .first() + + // If representative_row is still not found, create a new one + if (!representative_row) { + await db('representatives_meta_index').insert({ + account: representative_account_address, + timestamp: Math.floor(Date.now() / 1000) + }) + representative_row = await db('representatives_meta_index') + .where({ account: representative_account_address }) + .first() + } + } + + if (!representative_row) { + return 0 + } + + if (!representative_row.account) { + throw new Error('Representative is missing account address') + } + + const formatted_update = { + ...update, + account: representative_row.account + } + + const differences = diff(representative_row, formatted_update) + + const edits = differences.filter((d) => d.kind === 'E') + if (!edits.length) { + return 0 + } + + let changes_count = 0 + for (const edit of edits) { + const prop = edit.path[0] + + if (!editable_columns.includes(prop)) { + continue + } + + const is_null = !edit.rhs + const is_nullable = nullable_columns.includes(prop) + if (is_null && !is_nullable) { + continue + } + + if (excluded_columns.includes(prop)) { + log(`not allowed to edit ${prop}`) + continue + } + + log( + `updating representative: ${representative_row.account}, Column: ${prop}, Value: ${edit.lhs} => ${edit.rhs}` + ) + + const has_existing_value = edit.lhs + if (has_existing_value) { + await db('representatives_meta_index_changelog').insert({ + account: representative_row.account, + column: prop, + previous_value: edit.lhs, + new_value: edit.rhs, + timestamp: Math.floor(Date.now() / 1000) + }) + } + + await db('representatives_meta_index') + .update({ + [prop]: edit.rhs + }) + .where({ + account: representative_row.account + }) + + changes_count += 1 + } + + return changes_count +} diff --git a/package.json b/package.json index 4c9116e8..14ee3078 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,12 @@ "#api/*": "./api/*", "#root/*": "./*", "#common": "./common/index.mjs", + "#common/*": "./common/*", "#constants": "./constants.mjs", "#config": "./config.js", - "#db": "./db/index.mjs" + "#db": "./db/index.mjs", + "#libs-server": "./libs-server/index.mjs", + "#libs-server/*": "./libs-server/*" }, "scripts": { "dev": "concurrently \"yarn start\" \"yarn start:api\"", @@ -102,6 +105,7 @@ "fetch-cheerio-object": "^1.3.0", "front-matter": "^4.0.2", "fs-extra": "^11.2.0", + "inquirer": "^9.2.19", "jsonwebtoken": "^9.0.2", "knex": "^0.95.15", "markdown-it": "^12.3.2", @@ -110,8 +114,6 @@ "morgan": "^1.10.0", "morgan-debug": "^2.0.0", "mysql2": "^3.9.7", - "nanocurrency": "^2.5.0", - "nanocurrency-web": "^1.4.3", "node-cache": "^5.1.2", "node-cron": "^3.0.3", "node-fetch": "2.7.0", diff --git a/scripts/generate-account.mjs b/scripts/generate-account.mjs deleted file mode 100644 index 77aa5c8d..00000000 --- a/scripts/generate-account.mjs +++ /dev/null @@ -1,36 +0,0 @@ -import yargs from 'yargs' -import { hideBin } from 'yargs/helpers' -import nano from 'nanocurrency' -import { tools, wallet } from 'nanocurrency-web' - -const argv = yargs(hideBin(process.argv)).argv - -if (!argv.secret) { - console.log('missing --secret') - process.exit() -} - -const generateAccount = async () => { - let publicKey = argv.key - if (!argv.key) { - const w = wallet.generateLegacy() - const account = w.accounts[0] - publicKey = account.publicKey - } - - // sign new public key with account secret key - const signature = tools.sign(argv.secret, publicKey) - - const accountPublicKey = nano.derivePublicKey(argv.secret) - const accountAddress = nano.deriveAddress(accountPublicKey) - - console.log(`public key: ${publicKey}`) - console.log(`signature: ${signature}`) - console.log(`address: ${accountAddress}`) -} - -try { - generateAccount() -} catch (err) { - console.log(err) -} diff --git a/test/auth.register.key.test.mjs b/test/auth.register.key.test.mjs index 9a3ed034..69b236e3 100644 --- a/test/auth.register.key.test.mjs +++ b/test/auth.register.key.test.mjs @@ -2,11 +2,10 @@ import chai from 'chai' import chaiHTTP from 'chai-http' import ed25519 from '@trashman/ed25519-blake2b' -import nano from 'nanocurrency' import server from '#api/server.mjs' import knex from '#db' -import { sign_nano_community_link_key } from '#common' +import { sign_nano_community_link_key, encode_nano_address } from '#common' import { mochaGlobalSetup } from './global.mjs' process.env.NODE_ENV = 'test' @@ -32,9 +31,9 @@ describe('API /auth/register/key', () => { const nano_account_public_key = ed25519.publicKey( nano_account_private_key ) - const nano_account = nano.deriveAddress( - nano_account_public_key.toString('hex') - ) + const nano_account = encode_nano_address({ + public_key_buf: nano_account_public_key + }) const signature = sign_nano_community_link_key({ linked_public_key: public_key.toString('hex'), @@ -150,9 +149,12 @@ describe('API /auth/register/key', () => { ) const public_key = ed25519.publicKey(private_key) - const nano_account = nano.deriveAddress( - '0000000000000000000000000000000000000000000000000000000000000001' - ) + const nano_account = encode_nano_address({ + public_key_buf: Buffer.from( + '0000000000000000000000000000000000000000000000000000000000000001', + 'hex' + ) + }) // private key used is different from the stated nano account const nano_account_private_key = Buffer.from( diff --git a/test/auth.revoke.key.test.mjs b/test/auth.revoke.key.test.mjs index 78f12272..8c30f8b5 100644 --- a/test/auth.revoke.key.test.mjs +++ b/test/auth.revoke.key.test.mjs @@ -2,13 +2,13 @@ import chai from 'chai' import chaiHTTP from 'chai-http' import ed25519 from '@trashman/ed25519-blake2b' -import nano from 'nanocurrency' import server from '#api/server.mjs' import knex from '#db' import { sign_nano_community_link_key, - sign_nano_community_revoke_key + sign_nano_community_revoke_key, + encode_nano_address } from '#common' import { mochaGlobalSetup } from './global.mjs' @@ -21,27 +21,27 @@ describe('API /auth/revoke/key', () => { before(mochaGlobalSetup) describe('POST /api/auth/revoke/key', () => { - it('should register and then revoke an existing linked public key', async () => { - const private_key = Buffer.from( - '00000000000000000000000000000000000000000000000000000000000000000', - 'hex' - ) - const public_key = ed25519.publicKey(private_key) - + it('should register and then revoke an existing linked public key (using the account private key)', async () => { const nano_account_private_key = Buffer.from( - '00000000000000000000000000000000000000000000000000000000000000001', + '00000000000000000000000000000000000000000000000000000000000000000', 'hex' ) const nano_account_public_key = ed25519.publicKey( nano_account_private_key ) - const nano_account = nano.deriveAddress( - nano_account_public_key.toString('hex') + + const new_signing_private_key = Buffer.from( + '00000000000000000000000000000000000000000000000000000000000000001', + 'hex' ) + const new_signing_public_key = ed25519.publicKey(new_signing_private_key) + const nano_account = encode_nano_address({ + public_key_buf: nano_account_public_key + }) // Register/Link the key const link_signature = sign_nano_community_link_key({ - linked_public_key: public_key.toString('hex'), + linked_public_key: new_signing_public_key.toString('hex'), nano_account, nano_account_private_key, nano_account_public_key @@ -51,24 +51,92 @@ describe('API /auth/revoke/key', () => { .request(server) .post('/api/auth/register/key') .send({ - public_key: public_key.toString('hex'), + public_key: new_signing_public_key.toString('hex'), signature: link_signature.toString('hex'), account: nano_account }) // Revoke the key const revoke_signature = sign_nano_community_revoke_key({ - linked_public_key: public_key.toString('hex'), + linked_public_key: new_signing_public_key.toString('hex'), + either_private_key: nano_account_private_key, + either_public_key: nano_account_public_key.toString('hex') + }) + + const response = await chai + .request(server) + .post('/api/auth/revoke/key') + .send({ + public_key: new_signing_public_key.toString('hex'), + signature: revoke_signature.toString('hex'), + account: nano_account + }) + + expect(response).to.have.status(200) + + const revoked_row = await knex('account_keys') + .where({ public_key: new_signing_public_key.toString('hex') }) + .first() + + // eslint-disable-next-line no-unused-expressions + expect(revoked_row).to.exist + expect(revoked_row.account).to.equal(nano_account) + expect(revoked_row.public_key).to.equal( + new_signing_public_key.toString('hex') + ) + expect(revoked_row.revoke_signature).to.equal( + revoke_signature.toString('hex') + ) + expect(revoked_row.revoked_at).to.be.a('number') + }) + + it('should register and then revoke an existing linked public key (using the signing private key)', async () => { + const nano_account_private_key = Buffer.from( + '000000000000000000000000000000000000000000000000000000000000000FF', + 'hex' + ) + const nano_account_public_key = ed25519.publicKey( + nano_account_private_key + ) + + const new_signing_private_key = Buffer.from( + '00000000000000000000000000000000000000000000000000000000000000FFF', + 'hex' + ) + const new_signing_public_key = ed25519.publicKey(new_signing_private_key) + const nano_account = encode_nano_address({ + public_key_buf: nano_account_public_key + }) + + // Register/Link the key + const link_signature = sign_nano_community_link_key({ + linked_public_key: new_signing_public_key.toString('hex'), nano_account, nano_account_private_key, nano_account_public_key }) + await chai + .request(server) + .post('/api/auth/register/key') + .send({ + public_key: new_signing_public_key.toString('hex'), + signature: link_signature.toString('hex'), + account: nano_account + }) + + // Revoke the key using the signing private key + const revoke_signature = sign_nano_community_revoke_key({ + linked_public_key: new_signing_public_key.toString('hex'), + either_private_key: new_signing_private_key, + either_public_key: new_signing_public_key.toString('hex') + }) + const response = await chai .request(server) .post('/api/auth/revoke/key') .send({ - public_key: public_key.toString('hex'), + public_key: new_signing_public_key.toString('hex'), signature: revoke_signature.toString('hex'), account: nano_account }) @@ -76,13 +144,15 @@ describe('API /auth/revoke/key', () => { expect(response).to.have.status(200) const revoked_row = await knex('account_keys') - .where({ public_key: public_key.toString('hex') }) + .where({ public_key: new_signing_public_key.toString('hex') }) .first() // eslint-disable-next-line no-unused-expressions expect(revoked_row).to.exist expect(revoked_row.account).to.equal(nano_account) - expect(revoked_row.public_key).to.equal(public_key.toString('hex')) + expect(revoked_row.public_key).to.equal( + new_signing_public_key.toString('hex') + ) expect(revoked_row.revoke_signature).to.equal( revoke_signature.toString('hex') ) @@ -115,22 +185,13 @@ describe('API /auth/revoke/key', () => { expect(response.body.error).to.include('missing signature param') }) - it('should return 400 if account field is missing', async () => { - const response = await chai - .request(server) - .post('/api/auth/revoke/key') - .send({ - public_key: 'somepub', - signature: 'somesignature' - }) // missing account - expect(response).to.have.status(400) - expect(response.body.error).to.include('missing account param') - }) - it('should return 401 if public_key param is invalid', async () => { - const nano_account = nano.deriveAddress( - '0000000000000000000000000000000000000000000000000000000000000001' - ) + const nano_account = encode_nano_address({ + public_key_buf: Buffer.from( + '0000000000000000000000000000000000000000000000000000000000000001', + 'hex' + ) + }) const response = await chai .request(server) .post('/api/auth/revoke/key') @@ -143,56 +204,62 @@ describe('API /auth/revoke/key', () => { expect(response.body.error).to.equal('invalid public_key param') }) - it('should return 401 if account param is invalid', async () => { - const private_key = Buffer.from( - '00000000000000000000000000000000000000000000000000000000000000000', + it('should return 401 if signature is invalid', async () => { + // Generate a new account key pair + const new_account_private_key = Buffer.from( + '3000000000000000000000000000000000000000000000000000000000000000', 'hex' ) - const public_key = ed25519.publicKey(private_key) - const response = await chai - .request(server) - .post('/api/auth/revoke/key') - .send({ - public_key: public_key.toString('hex'), - signature: 'somesignature', - account: 'invalidaccount' - }) - expect(response).to.have.status(401) - expect(response.body.error).to.equal('invalid account param') - }) + const new_account_public_key = ed25519.publicKey(new_account_private_key) + const new_nano_account = encode_nano_address({ + public_key_buf: new_account_public_key + }) - it('should return 401 if signature is invalid', async () => { - const private_key = Buffer.from( - '00000000000000000000000000000000000000000000000000000000000000000', + // Generate a new signing key pair + const new_signing_private_key = Buffer.from( + '4000000000000000000000000000000000000000000000000000000000000000', 'hex' ) - const public_key = ed25519.publicKey(private_key) + const new_signing_public_key = ed25519.publicKey(new_signing_private_key) - const nano_account = nano.deriveAddress( - '0000000000000000000000000000000000000000000000000000000000000000' - ) + // Generate a link signature for the new signing key using the new account key + const link_signature = sign_nano_community_link_key({ + linked_public_key: new_signing_public_key.toString('hex'), + nano_account: new_nano_account, + nano_account_private_key: new_account_private_key, + nano_account_public_key: new_account_public_key + }) - const nano_account_different_private_key = Buffer.from( - '00000000000000000000000000000000000000000000000000000000000000001', + // Register the new signing key with the link signature + await chai + .request(server) + .post('/api/auth/register/key') + .send({ + public_key: new_signing_public_key.toString('hex'), + signature: link_signature.toString('hex'), + account: new_nano_account + }) + + // Attempt to revoke with an invalid signature + const invalid_private_key = Buffer.from( + '2000000000000000000000000000000000000000000000000000000000000000', 'hex' ) - const nano_account_different_public_key = ed25519.publicKey( - nano_account_different_private_key - ) - const signature = sign_nano_community_revoke_key({ - linked_public_key: public_key.toString('hex'), - nano_account, - nano_account_private_key: nano_account_different_private_key, - nano_account_public_key: nano_account_different_public_key + const invalid_signature = sign_nano_community_revoke_key({ + linked_public_key: new_signing_public_key.toString('hex'), + either_private_key: invalid_private_key, + either_public_key: new_signing_public_key.toString('hex') }) + const response = await chai .request(server) .post('/api/auth/revoke/key') .send({ - public_key: public_key.toString('hex'), - signature: signature.toString('hex'), - account: nano_account + public_key: new_signing_public_key.toString('hex'), + signature: invalid_signature.toString('hex'), + account: new_nano_account }) + expect(response).to.have.status(401) expect(response.body.error).to.equal('invalid signature') }) diff --git a/test/cli.test.mjs b/test/cli.test.mjs new file mode 100644 index 00000000..9bcc3586 --- /dev/null +++ b/test/cli.test.mjs @@ -0,0 +1,325 @@ +/* global describe, before, after, it */ +import chai from 'chai' +import { exec, spawn } from 'child_process' +import util from 'util' +import nock from 'nock' + +import server from '#api/server.mjs' +import config from '#config' +import db from '#db' +import { + REPRESENTATIVE_TRACKING_MINIMUM_VOTING_WEIGHT, + ACCOUNT_TRACKING_MINIMUM_BALANCE +} from '#constants' + +const { port } = config +const exec_promise = util.promisify(exec) + +process.env.NODE_ENV = 'test' +const expect = chai.expect + +function strip_ansi_escape_codes(str) { + const last_newline_index = str.lastIndexOf('\n') + if (last_newline_index !== -1) { + str = str.substring(last_newline_index + 1) + } + return str + .replace( + // eslint-disable-next-line no-control-regex + /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, + '' + ) + .trim() +} + +describe('CLI', function () { + this.timeout(30000) + + let new_signing_key + const nano_private_key = + '1111111111111111111111111111111111111111111111111111111111111111' + const nano_account_address = + 'nano_3d78japo7ziqqcsptk47eonzwzwjyaydcywq5ebzowjpxgyehynnjc9pd5zj' + + before(() => { + process.env.NC_CLI_NANO_PRIVATE_KEY = nano_private_key + + server.listen(port, () => console.log(`API listening on port ${port}`)) + }) + + after(() => { + server.close() + }) + + describe('add-signing-key command', () => { + it('should add a new signing key', async () => { + const { stdout, stderr } = await exec_promise( + 'node cli/index.mjs add-signing-key' + ) + + // eslint-disable-next-line no-unused-expressions + expect(stderr).to.be.empty + expect(stdout).to.include('Key registration successful') + + // Extract the new signing key from stdout + const match = stdout.match(/linked_public_key: '([a-f0-9]+)'/) + if (match) { + new_signing_key = match[1] // Simplified extraction logic + } + }) + }) + + describe('revoke-signing-key command', () => { + it('should revoke an existing signing key', async () => { + if (!new_signing_key) { + throw new Error('No new signing key found for revocation test') + } + const { stdout, stderr } = await exec_promise( + `echo y | node cli/index.mjs revoke-signing-key ${new_signing_key}` + ) + // eslint-disable-next-line no-unused-expressions + expect(stderr).to.be.empty + expect(stdout).to.include('Key revocation successful') + }) + }) + + describe('update-rep-meta operation', () => { + it('should send a message for update-rep-meta operation and check database updates', async () => { + let stdout = '' + let stderr = '' + const expected_alias = 'TestNodeAlias' + const expected_description = 'A test node for development purposes.' + const expected_donation_address = + 'nano_3niceeeyiaaif5xoiqjvth5gqrypuwytrm867asbciw3ndz8j3mazqqk6cok' + const expected_cpu_model = 'Intel i7' + const expected_cpu_cores = 4 + const expected_ram = 16 + const expected_reddit = 'test_reddit_user' + const expected_twitter = 'test_twitter_user' + const expected_discord = 'test_discord_user#1234' + const expected_github = 'test_github_user' + const expected_email = 'test@example.com' + const expected_website = 'https://example.com' + + try { + // mock the account_info rpc request needed for message storing + nock('http://nano:7076') + .post('/', (body) => body.action === 'account_info') + .reply(200, { + weight: String(REPRESENTATIVE_TRACKING_MINIMUM_VOTING_WEIGHT) + }) + + const child = spawn('node', ['cli/index.mjs', 'update-rep-meta'], { + stdio: ['pipe', 'pipe', 'pipe'] + }) + child.stdin.setDefaultEncoding('utf-8') + child.stdout.on('data', (data) => { + const output = strip_ansi_escape_codes(data.toString()) + + switch (output) { + case '? Alias:': + child.stdin.write(`${expected_alias}\n`) + break + case '? Description:': + child.stdin.write(`${expected_description}\n`) + break + case '? Donation Address:': + child.stdin.write(`${expected_donation_address}\n`) + break + case '? CPU Model:': + child.stdin.write(`${expected_cpu_model}\n`) + break + case '? CPU Cores:': + child.stdin.write(`${expected_cpu_cores}\n`) + break + case '? RAM Amount (GB):': + child.stdin.write(`${expected_ram}\n`) + break + case '? Reddit Username:': + child.stdin.write(`${expected_reddit}\n`) + break + case '? Twitter Username:': + child.stdin.write(`${expected_twitter}\n`) + break + case '? Discord Username:': + child.stdin.write(`${expected_discord}\n`) + break + case '? GitHub Username:': + child.stdin.write(`${expected_github}\n`) + break + case '? Email:': + child.stdin.write(`${expected_email}\n`) + break + case '? Website URL:': + child.stdin.write(`${expected_website}\n`) + break + case '? Would you like to edit any field? (y/N)': + child.stdin.write('N\n') + break + case '? Confirm? (y/n)': + child.stdin.write('y\n') + child.stdin.end() + break + } + }) + + child.stderr.on('data', (data) => { + stderr += data.toString() + }) + + child.stdout.on('data', (data) => { + stdout += data.toString() + }) + + const exit_code = await new Promise((resolve) => { + child.on('close', resolve) + }) + + // eslint-disable-next-line no-unused-expressions + expect(stderr).to.be.empty + expect(exit_code).to.equal(0) + + // Check database for updated columns + const updated_account = await db('accounts') + .where({ account: nano_account_address }) + .first() + expect(updated_account.alias).to.equal(expected_alias) + + const updated_representative = await db('representatives_meta_index') + .where({ account: nano_account_address }) + .first() + + expect(updated_representative.description).to.equal( + expected_description + ) + expect(updated_representative.donation_address).to.equal( + expected_donation_address + ) + expect(updated_representative.cpu_model).to.equal(expected_cpu_model) + expect(updated_representative.cpu_cores).to.equal(expected_cpu_cores) + expect(updated_representative.ram).to.equal(expected_ram) + expect(updated_representative.reddit).to.equal(expected_reddit) + expect(updated_representative.twitter).to.equal(expected_twitter) + expect(updated_representative.discord).to.equal(expected_discord) + expect(updated_representative.github).to.equal(expected_github) + expect(updated_representative.email).to.equal(expected_email) + expect(updated_representative.website).to.equal(expected_website) + } catch (err) { + console.log(err) + console.log(stderr) + console.log(stdout) + throw err + } + }) + }) + + describe('update-account-meta operation', () => { + it('should send a message for update-account-meta operation', async () => { + let stderr = '' + let stdout = '' + try { + // mock the account_info rpc request needed for message storing + nock('http://nano:7076') + .post('/', (body) => body.action === 'account_info') + .reply(200, { + balance: String(ACCOUNT_TRACKING_MINIMUM_BALANCE) + }) + + const child = spawn('node', ['cli/index.mjs', 'update-account-meta'], { + stdio: ['pipe', 'pipe', 'pipe'] + }) + child.stdin.setDefaultEncoding('utf-8') + child.stdout.on('data', (data) => { + const output = strip_ansi_escape_codes(data.toString().trim()) + switch (output) { + case '? Alias:': + child.stdin.write('Alias\n') + break + case '? Would you like to edit any field? (y/N)': + child.stdin.write('n\n') + break + case '? Confirm? (y/n)': + child.stdin.write('n\n') + child.stdin.end() + break + } + }) + + child.stderr.on('data', (data) => { + stderr += data.toString() + }) + + child.stdout.on('data', (data) => { + stdout += data.toString() + }) + + const exit_code = await new Promise((resolve) => { + child.on('close', resolve) + }) + + // eslint-disable-next-line no-unused-expressions + expect(stderr).to.be.empty + expect(exit_code).to.equal(0) + } catch (err) { + console.log(err) + console.log(stdout) + console.log(stderr) + throw err + } + }) + }) + + describe('update-block-meta operation', () => { + it('should send a message for update-block-meta operation', async () => { + let stdout = '' + let stderr = '' + const block_hash = + '943E3EED4F340ECBF7E06FA2E74A3E17B1DC4148C6913403B8ACFE7FBB1C2139' + + try { + const child = spawn( + 'node', + ['cli/index.mjs', 'update-block-meta', block_hash], + { stdio: ['pipe', 'pipe', 'pipe'] } + ) + child.stdin.setDefaultEncoding('utf-8') + child.stdout.on('data', (data) => { + const output = strip_ansi_escape_codes(data.toString().trim()) + switch (output) { + case '? Note:': + child.stdin.write('Test note for update-block-meta operation\n') + break + case '? Would you like to edit any field? (y/N)': + child.stdin.write('n\n') + break + case '? Confirm? (y/n)': + child.stdin.write('n\n') + child.stdin.end() + break + } + }) + + child.stderr.on('data', (data) => { + stderr += data.toString() + }) + + child.stdout.on('data', (data) => { + stdout += data.toString() + }) + + const exit_code = await new Promise((resolve) => { + child.on('close', resolve) + }) + + // eslint-disable-next-line no-unused-expressions + expect(stderr).to.be.empty + expect(exit_code).to.equal(0) + } catch (err) { + console.log(err) + console.log(stderr) + console.log(stdout) + throw err + } + }) + }) +}) diff --git a/yarn.lock b/yarn.lock index 63c8a5b3..228e0020 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3020,6 +3020,13 @@ __metadata: languageName: node linkType: hard +"@inquirer/figures@npm:^1.0.1": + version: 1.0.1 + resolution: "@inquirer/figures@npm:1.0.1" + checksum: e428dac4921c12fa65f1e2f2846f3fdb2c8ea98ef250e8758bc117d67625496bf1f844b67364e69815b6a8d9b2b0857c9864aec5aebb8a92fc3408d16ccdcc39 + languageName: node + linkType: hard + "@ipld/car@npm:^3.0.1, @ipld/car@npm:^3.2.3": version: 3.2.4 resolution: "@ipld/car@npm:3.2.4" @@ -3204,6 +3211,15 @@ __metadata: languageName: node linkType: hard +"@ljharb/through@npm:^2.3.13": + version: 2.3.13 + resolution: "@ljharb/through@npm:2.3.13" + dependencies: + call-bind: ^1.0.7 + checksum: 0255464a0ec7901b08cff3e99370b87e66663f46249505959c0cb4f6121095d533bbb7c7cda338063d3e134cbdd721e2705bc18eac7611b4f9ead6e7935d13ba + languageName: node + linkType: hard + "@mui/base@npm:5.0.0-beta.40": version: 5.0.0-beta.40 resolution: "@mui/base@npm:5.0.0-beta.40" @@ -5317,7 +5333,7 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.2.1": +"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.2": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" dependencies: @@ -5816,13 +5832,6 @@ __metadata: languageName: node linkType: hard -"bignumber.js@npm:9.0.2": - version: 9.0.2 - resolution: "bignumber.js@npm:9.0.2" - checksum: 8637b71d0a99104b20413c47578953970006fec6b4df796b9dcfd9835ea9c402ea0e727eba9a5ca9f9a393c1d88b6168c5bbe0887598b708d4f8b4870ad62e1f - languageName: node - linkType: hard - "bignumber.js@npm:^9.0.0": version: 9.0.1 resolution: "bignumber.js@npm:9.0.1" @@ -5912,6 +5921,17 @@ __metadata: languageName: node linkType: hard +"bl@npm:^4.1.0": + version: 4.1.0 + resolution: "bl@npm:4.1.0" + dependencies: + buffer: ^5.5.0 + inherits: ^2.0.4 + readable-stream: ^3.4.0 + checksum: 9e8521fa7e83aa9427c6f8ccdcba6e8167ef30cc9a22df26effcc5ab682ef91d2cbc23a239f945d099289e4bbcfae7a192e9c28c84c6202e710a0dfec3722662 + languageName: node + linkType: hard + "bl@npm:^5.0.0": version: 5.1.0 resolution: "bl@npm:5.1.0" @@ -5923,13 +5943,6 @@ __metadata: languageName: node linkType: hard -"blakejs@npm:1.2.1": - version: 1.2.1 - resolution: "blakejs@npm:1.2.1" - checksum: d699ba116cfa21d0b01d12014a03e484dd76d483133e6dc9eb415aa70a119f08beb3bcefb8c71840106a00b542cba77383f8be60cd1f0d4589cb8afb922eefbe - languageName: node - linkType: hard - "blakejs@npm:^1.1.0": version: 1.1.0 resolution: "blakejs@npm:1.1.0" @@ -6219,7 +6232,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.2.1": +"buffer@npm:^5.2.1, buffer@npm:^5.5.0": version: 5.7.1 resolution: "buffer@npm:5.7.1" dependencies: @@ -6264,13 +6277,6 @@ __metadata: languageName: node linkType: hard -"byte-base64@npm:1.1.0": - version: 1.1.0 - resolution: "byte-base64@npm:1.1.0" - checksum: 95851fc758882ba6b28422d70c685f86257eb635be10eb37528765f39fc1c8766972f9bb39e0684539fafc7d9625145d26e5ab310a52c9eb9df3506952586e0c - languageName: node - linkType: hard - "byte-size@npm:^8.1.0": version: 8.1.1 resolution: "byte-size@npm:8.1.1" @@ -6616,6 +6622,20 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.3.0": + version: 5.3.0 + resolution: "chalk@npm:5.3.0" + checksum: 623922e077b7d1e9dedaea6f8b9e9352921f8ae3afe739132e0e00c275971bdd331268183b2628cf4ab1727c45ea1f28d7e24ac23ce1db1eb653c414ca8a5a80 + languageName: node + linkType: hard + +"chardet@npm:^0.7.0": + version: 0.7.0 + resolution: "chardet@npm:0.7.0" + checksum: 6fd5da1f5d18ff5712c1e0aed41da200d7c51c28f11b36ee3c7b483f3696dabc08927fc6b227735eb8f0e1215c9a8abd8154637f3eff8cada5959df7f58b024d + languageName: node + linkType: hard + "charset@npm:^1.0.1": version: 1.0.1 resolution: "charset@npm:1.0.1" @@ -6800,6 +6820,29 @@ __metadata: languageName: node linkType: hard +"cli-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "cli-cursor@npm:3.1.0" + dependencies: + restore-cursor: ^3.1.0 + checksum: 2692784c6cd2fd85cfdbd11f53aea73a463a6d64a77c3e098b2b4697a20443f430c220629e1ca3b195ea5ac4a97a74c2ee411f3807abf6df2b66211fec0c0a29 + languageName: node + linkType: hard + +"cli-spinners@npm:^2.5.0": + version: 2.9.2 + resolution: "cli-spinners@npm:2.9.2" + checksum: 1bd588289b28432e4676cb5d40505cfe3e53f2e4e10fbe05c8a710a154d6fe0ce7836844b00d6858f740f2ffe67cdc36e0fce9c7b6a8430e80e6388d5aa4956c + languageName: node + linkType: hard + +"cli-width@npm:^4.1.0": + version: 4.1.0 + resolution: "cli-width@npm:4.1.0" + checksum: 0a79cff2dbf89ef530bcd54c713703ba94461457b11e5634bd024c78796ed21401e32349c004995954e06f442d82609287e7aabf6a5f02c919a1cf3b9b6854ff + languageName: node + linkType: hard + "clipboardy@npm:^2.3.0": version: 2.3.0 resolution: "clipboardy@npm:2.3.0" @@ -6860,6 +6903,13 @@ __metadata: languageName: node linkType: hard +"clone@npm:^1.0.2": + version: 1.0.4 + resolution: "clone@npm:1.0.4" + checksum: d06418b7335897209e77bdd430d04f882189582e67bd1f75a04565f3f07f5b3f119a9d670c943b6697d0afb100f03b866b3b8a1f91d4d02d72c4ecf2bb64b5dd + languageName: node + linkType: hard + "clsx@npm:^2.0.0, clsx@npm:^2.1.0": version: 2.1.0 resolution: "clsx@npm:2.1.0" @@ -7313,13 +7363,6 @@ __metadata: languageName: node linkType: hard -"crypto-js@npm:3.1.9-1": - version: 3.1.9-1 - resolution: "crypto-js@npm:3.1.9-1" - checksum: 2dc7a7024fd02a88ef3f673e949c7a98d0d61da8b57c95a8207e22d8dd2135b29124bc28dfb2770d8a5f3cf8a46d81e68b3a4bde982a71bae479a9ce666f4cd8 - languageName: node - linkType: hard - "crypto-random-string@npm:^2.0.0": version: 2.0.0 resolution: "crypto-random-string@npm:2.0.0" @@ -7821,6 +7864,15 @@ __metadata: languageName: node linkType: hard +"defaults@npm:^1.0.3": + version: 1.0.4 + resolution: "defaults@npm:1.0.4" + dependencies: + clone: ^1.0.2 + checksum: 3a88b7a587fc076b84e60affad8b85245c01f60f38fc1d259e7ac1d89eb9ce6abb19e27215de46b98568dd5bc48471730b327637e6f20b0f1bc85cf00440c80a + languageName: node + linkType: hard + "defer-to-connect@npm:^1.0.1": version: 1.1.3 resolution: "defer-to-connect@npm:1.1.3" @@ -9399,6 +9451,17 @@ __metadata: languageName: node linkType: hard +"external-editor@npm:^3.1.0": + version: 3.1.0 + resolution: "external-editor@npm:3.1.0" + dependencies: + chardet: ^0.7.0 + iconv-lite: ^0.4.24 + tmp: ^0.0.33 + checksum: 1c2a616a73f1b3435ce04030261bed0e22d4737e14b090bb48e58865da92529c9f2b05b893de650738d55e692d071819b45e1669259b2b354bc3154d27a698c7 + languageName: node + linkType: hard + "extract-zip@npm:^1.6.6": version: 1.7.0 resolution: "extract-zip@npm:1.7.0" @@ -11089,7 +11152,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.4.24": +"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" dependencies: @@ -11391,6 +11454,29 @@ __metadata: languageName: node linkType: hard +"inquirer@npm:^9.2.19": + version: 9.2.19 + resolution: "inquirer@npm:9.2.19" + dependencies: + "@inquirer/figures": ^1.0.1 + "@ljharb/through": ^2.3.13 + ansi-escapes: ^4.3.2 + chalk: ^5.3.0 + cli-cursor: ^3.1.0 + cli-width: ^4.1.0 + external-editor: ^3.1.0 + lodash: ^4.17.21 + mute-stream: 1.0.0 + ora: ^5.4.1 + run-async: ^3.0.0 + rxjs: ^7.8.1 + string-width: ^4.2.3 + strip-ansi: ^6.0.1 + wrap-ansi: ^6.2.0 + checksum: 2bcfbed4293e4f0af85c240e86b192bba5dae6a8df6d01d7cddf54b76d8df5dd5706d67cd2e5934f679973311ab74228f25fb0237a58d63e44add425e93da3a6 + languageName: node + linkType: hard + "interface-blockstore@npm:^2.0.2, interface-blockstore@npm:^2.0.3": version: 2.0.3 resolution: "interface-blockstore@npm:2.0.3" @@ -12171,6 +12257,13 @@ __metadata: languageName: node linkType: hard +"is-interactive@npm:^1.0.0": + version: 1.0.0 + resolution: "is-interactive@npm:1.0.0" + checksum: 824808776e2d468b2916cdd6c16acacebce060d844c35ca6d82267da692e92c3a16fdba624c50b54a63f38bdc4016055b6f443ce57d7147240de4f8cdabaf6f9 + languageName: node + linkType: hard + "is-ip@npm:^2.0.0": version: 2.0.0 resolution: "is-ip@npm:2.0.0" @@ -13503,7 +13596,7 @@ __metadata: languageName: node linkType: hard -"log-symbols@npm:4.1.0": +"log-symbols@npm:4.1.0, log-symbols@npm:^4.1.0": version: 4.1.0 resolution: "log-symbols@npm:4.1.0" dependencies: @@ -14499,6 +14592,13 @@ __metadata: languageName: node linkType: hard +"mute-stream@npm:1.0.0": + version: 1.0.0 + resolution: "mute-stream@npm:1.0.0" + checksum: 36fc968b0e9c9c63029d4f9dc63911950a3bdf55c9a87f58d3a266289b67180201cade911e7699f8b2fa596b34c9db43dad37649e3f7fdd13c3bb9edb0017ee7 + languageName: node + linkType: hard + "mysql2@npm:^3.9.7": version: 3.9.7 resolution: "mysql2@npm:3.9.7" @@ -14524,28 +14624,6 @@ __metadata: languageName: node linkType: hard -"nanocurrency-web@npm:^1.4.3": - version: 1.4.3 - resolution: "nanocurrency-web@npm:1.4.3" - dependencies: - bignumber.js: 9.0.2 - blakejs: 1.2.1 - byte-base64: 1.1.0 - crypto-js: 3.1.9-1 - checksum: 62cb82d651259a137e03e27d6a4be3910dd21f7b41fc36b2e7957dd9320534c7cbafed392d4285ff37a944296b7d5cabb261fe13c7fdcdee72285d3cc9019330 - languageName: node - linkType: hard - -"nanocurrency@npm:^2.5.0": - version: 2.5.0 - resolution: "nanocurrency@npm:2.5.0" - dependencies: - bignumber.js: ^9.0.0 - blakejs: ^1.1.0 - checksum: 5db534bc725438dcaec2c858ff93c6c730d959a1feaa8cbc0cfe8dabb562477723655d982692cfeaca7ce7e3f7635afb10aac55344319635b2061979e133ba8d - languageName: node - linkType: hard - "nanoid@npm:^3.0.2": version: 3.1.22 resolution: "nanoid@npm:3.1.22" @@ -15123,6 +15201,23 @@ __metadata: languageName: node linkType: hard +"ora@npm:^5.4.1": + version: 5.4.1 + resolution: "ora@npm:5.4.1" + dependencies: + bl: ^4.1.0 + chalk: ^4.1.0 + cli-cursor: ^3.1.0 + cli-spinners: ^2.5.0 + is-interactive: ^1.0.0 + is-unicode-supported: ^0.1.0 + log-symbols: ^4.1.0 + strip-ansi: ^6.0.0 + wcwidth: ^1.0.1 + checksum: 28d476ee6c1049d68368c0dc922e7225e3b5600c3ede88fade8052837f9ed342625fdaa84a6209302587c8ddd9b664f71f0759833cbdb3a4cf81344057e63c63 + languageName: node + linkType: hard + "os-filter-obj@npm:^2.0.0": version: 2.0.0 resolution: "os-filter-obj@npm:2.0.0" @@ -15132,6 +15227,13 @@ __metadata: languageName: node linkType: hard +"os-tmpdir@npm:~1.0.2": + version: 1.0.2 + resolution: "os-tmpdir@npm:1.0.2" + checksum: 5666560f7b9f10182548bf7013883265be33620b1c1b4a4d405c25be2636f970c5488ff3e6c48de75b55d02bde037249fe5dbfbb4c0fb7714953d56aed062e6d + languageName: node + linkType: hard + "ow@npm:^0.17.0": version: 0.17.0 resolution: "ow@npm:0.17.0" @@ -16956,6 +17058,16 @@ __metadata: languageName: node linkType: hard +"restore-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "restore-cursor@npm:3.1.0" + dependencies: + onetime: ^5.1.0 + signal-exit: ^3.0.2 + checksum: f877dd8741796b909f2a82454ec111afb84eb45890eb49ac947d87991379406b3b83ff9673a46012fca0d7844bb989f45cc5b788254cf1a39b6b5a9659de0630 + languageName: node + linkType: hard + "retimer@npm:^2.0.0": version: 2.0.0 resolution: "retimer@npm:2.0.0" @@ -17082,6 +17194,7 @@ __metadata: html-loader: ^5.0.0 html-webpack-plugin: ^5.6.0 image-webpack-loader: ^8.1.0 + inquirer: ^9.2.19 ipfs-deploy: ^12.0.1 jsonwebtoken: ^9.0.2 jss: ^10.10.0 @@ -17094,8 +17207,6 @@ __metadata: morgan: ^1.10.0 morgan-debug: ^2.0.0 mysql2: ^3.9.7 - nanocurrency: ^2.5.0 - nanocurrency-web: ^1.4.3 nib: ^1.2.0 nock: ^13.5.4 node-cache: ^5.1.2 @@ -17138,6 +17249,13 @@ __metadata: languageName: node linkType: hard +"run-async@npm:^3.0.0": + version: 3.0.0 + resolution: "run-async@npm:3.0.0" + checksum: 280c03d5a88603f48103fc6fd69f07fb0c392a1e0d319c34ec96a2516030e07ba06f79231a563c78698b882649c2fc1fda601bc84705f57d50efcd1fa506cfc0 + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -18663,6 +18781,15 @@ __metadata: languageName: node linkType: hard +"tmp@npm:^0.0.33": + version: 0.0.33 + resolution: "tmp@npm:0.0.33" + dependencies: + os-tmpdir: ~1.0.2 + checksum: 902d7aceb74453ea02abbf58c203f4a8fc1cead89b60b31e354f74ed5b3fb09ea817f94fb310f884a5d16987dd9fa5a735412a7c2dd088dd3d415aa819ae3a28 + languageName: node + linkType: hard + "to-buffer@npm:^1.1.1": version: 1.1.1 resolution: "to-buffer@npm:1.1.1" @@ -19406,6 +19533,15 @@ __metadata: languageName: node linkType: hard +"wcwidth@npm:^1.0.1": + version: 1.0.1 + resolution: "wcwidth@npm:1.0.1" + dependencies: + defaults: ^1.0.3 + checksum: 814e9d1ddcc9798f7377ffa448a5a3892232b9275ebb30a41b529607691c0491de47cba426e917a4d08ded3ee7e9ba2f3fe32e62ee3cd9c7d3bafb7754bd553c + languageName: node + linkType: hard + "web-encoding@npm:1.1.5": version: 1.1.5 resolution: "web-encoding@npm:1.1.5" @@ -19770,6 +19906,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: 6cd96a410161ff617b63581a08376f0cb9162375adeb7956e10c8cd397821f7eb2a6de24eb22a0b28401300bf228c86e50617cd568209b5f6775b93c97d2fe3a + languageName: node + linkType: hard + "wrap-ansi@npm:^8.1.0": version: 8.1.0 resolution: "wrap-ansi@npm:8.1.0"