From 657b73f93d8323c6bc877cf8decf05debf2a6213 Mon Sep 17 00:00:00 2001 From: Oli Evans Date: Wed, 10 Jan 2024 16:29:53 +0000 Subject: [PATCH 1/3] feat: `w3 key create` Print a new key pair. Does not change your current signing key. Bring the feature from [`ucan-key`](https://github.com/olizilla/ucan-key) into w3cli see: https://github.com/web3-storage/w3cli/issues/154#issuecomment-1885042715 License: MIT Signed-off-by: Oli Evans --- README.md | 137 +++++++++++++++++++++++++---------------------- bin.js | 7 +++ index.js | 42 ++++++++++++--- test/bin.spec.js | 20 ++++--- 4 files changed, 128 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 0fc8bc9..32cc4f4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 💾 the `w3` command line interface. -## Getting started +## Getting started Install the CLI from npm (**requires Node 18 or higher**): @@ -25,47 +25,49 @@ w3 space register # defaults to registering you with web3.storage If you'd like to learn more about what is going on under the hood with w3up and its use of Spaces, [UCANs](https://ucan.xyz/), and more, check out the `w3up-client` README [here](https://github.com/web3-storage/w3up/tree/main/packages/w3up-client#usage). - Upload a file or directory: ```console w3 up recipies.txt ``` -> ⚠️❗ __Public Data__ 🌎: All data uploaded to w3up is available to anyone who requests it using the correct CID. Do not store any private or sensitive information in an unencrypted form using w3up. -> ⚠️❗ __Permanent Data__ ♾️: Removing files from w3up will remove them from the file listing for your account, but that doesn’t prevent nodes on the decentralized storage network from retaining copies of the data indefinitely. Do not use w3up for data that may need to be permanently deleted in the future. +> ⚠️❗ **Public Data** 🌎: All data uploaded to w3up is available to anyone who requests it using the correct CID. Do not store any private or sensitive information in an unencrypted form using w3up. + +> ⚠️❗ **Permanent Data** ♾️: Removing files from w3up will remove them from the file listing for your account, but that doesn’t prevent nodes on the decentralized storage network from retaining copies of the data indefinitely. Do not use w3up for data that may need to be permanently deleted in the future. ## Commands -* Basics - * [`w3 login`](#w3-login-email) - * [`w3 up`](#w3-up-path-path) - * [`w3 ls`](#w3-ls) - * [`w3 rm`](#w3-rm-root-cid) - * [`w3 open`](#w3-open-cid) - * [`w3 whoami`](#w3-whoami) -* Space management - * [`w3 space add`](#w3-space-add-proofucan) - * [`w3 space create`](#w3-space-create-name) - * [`w3 space ls`](#w3-space-ls) - * [`w3 space register`](#w3-space-register) - * [`w3 space use`](#w3-space-use-did) - * [`w3 space info`](#w3-space-info) -* Capability management - * [`w3 delegation create`](#w3-delegation-create-audience-did) - * [`w3 delegation ls`](#w3-delegation-ls) - * [`w3 delegation revoke`](#w3-delegation-revoke-delegation-cid) - * [`w3 proof add`](#w3-proof-add-proofucan) - * [`w3 proof ls`](#w3-proof-ls) -* Advanced usage - * [`w3 can space info`](#w3-can-space-info-did) coming soon! - * [`w3 can space recover`](#w3-can-space-recover-email) coming soon! - * [`w3 can store add`](#w3-can-store-add-car-path) - * [`w3 can store ls`](#w3-can-store-ls) - * [`w3 can store rm`](#w3-can-store-rm-car-cid) - * [`w3 can upload add`](#w3-can-upload-add-root-cid-shard-cid-shard-cid) - * [`w3 can upload ls`](#w3-can-upload-ls) - * [`w3 can upload rm`](#w3-can-upload-rm-root-cid) +- Basics + - [`w3 login`](#w3-login-email) + - [`w3 up`](#w3-up-path-path) + - [`w3 ls`](#w3-ls) + - [`w3 rm`](#w3-rm-root-cid) + - [`w3 open`](#w3-open-cid) + - [`w3 whoami`](#w3-whoami) +- Space management + - [`w3 space add`](#w3-space-add-proofucan) + - [`w3 space create`](#w3-space-create-name) + - [`w3 space ls`](#w3-space-ls) + - [`w3 space register`](#w3-space-register) + - [`w3 space use`](#w3-space-use-did) + - [`w3 space info`](#w3-space-info) +- Capability management + - [`w3 delegation create`](#w3-delegation-create-audience-did) + - [`w3 delegation ls`](#w3-delegation-ls) + - [`w3 delegation revoke`](#w3-delegation-revoke-delegation-cid) + - [`w3 proof add`](#w3-proof-add-proofucan) + - [`w3 proof ls`](#w3-proof-ls) +- Key management +- [`w3 key create`](#w3-key-create) +- Advanced usage + - [`w3 can space info`](#w3-can-space-info-did) coming soon! + - [`w3 can space recover`](#w3-can-space-recover-email) coming soon! + - [`w3 can store add`](#w3-can-store-add-car-path) + - [`w3 can store ls`](#w3-can-store-ls) + - [`w3 can store rm`](#w3-can-store-rm-car-cid) + - [`w3 can upload add`](#w3-can-upload-add-root-cid-shard-cid-shard-cid) + - [`w3 can upload ls`](#w3-can-upload-ls) + - [`w3 can upload rm`](#w3-can-upload-rm-root-cid) --- @@ -77,24 +79,24 @@ Authenticate this agent with your email address to get access to all capabilitie Upload file(s) to web3.storage. The IPFS Content ID (CID) for your files is calculated on your machine, and sent up along with your files. web3.storage makes your content available on the IPFS network -* `--no-wrap` Don't wrap input files with a directory. -* `-H, --hidden` Include paths that start with ".". -* `-c, --car` File is a CAR file. -* `--shard-size` Shard uploads into CAR files of approximately this size in bytes. -* `--concurrent-requests` Send up to this many CAR shards concurrently. +- `--no-wrap` Don't wrap input files with a directory. +- `-H, --hidden` Include paths that start with ".". +- `-c, --car` File is a CAR file. +- `--shard-size` Shard uploads into CAR files of approximately this size in bytes. +- `--concurrent-requests` Send up to this many CAR shards concurrently. ### `w3 ls` List all the uploads registered in the current space. -* `--json` Format as newline delimited JSON -* `--shards` Pretty print with shards in output +- `--json` Format as newline delimited JSON +- `--shards` Pretty print with shards in output ### `w3 rm ` Remove an upload from the uploads listing. Note that this command does not remove the data from the IPFS network, nor does it remove it from space storage (by default). -* `--shards` Also remove all shards referenced by the upload from the store. Use with caution and ensure other uploads do not reference the same shards. +- `--shards` Also remove all shards referenced by the upload from the store. Use with caution and ensure other uploads do not reference the same shards. ### `w3 open ` @@ -126,14 +128,14 @@ List spaces known to the agent. ### `w3 space register` -Register the space by adding a storage provider and delegating all of its +Register the space by adding a storage provider and delegating all of its capabilities to the currently logged in account. If you are authorized against more than one account you'll need to pass the `--email` option to specify which account to register the space with. -* `--email` The email address of the account to associate this space with. -* `--provider` The storage provider to associate with this space. -> By registering your w3up beta Space with [web3.storage](http://web3.storage/), you agree to the w3up beta [Terms of Service](https://console.web3.storage/terms). Until the beta period is over and this migration occurs, uploads to w3up will not appear in your web3.storage account (and vice versa), even if you register with the same email. +- `--email` The email address of the account to associate this space with. +- `--provider` The storage provider to associate with this space. + > By registering your w3up beta Space with [web3.storage](http://web3.storage/), you agree to the w3up beta [Terms of Service](https://console.web3.storage/terms). Until the beta period is over and this migration occurs, uploads to w3up will not appear in your web3.storage account (and vice versa), even if you register with the same email. ### `w3 space use ` @@ -144,17 +146,17 @@ Set the current space in use by the agent. Get information about a space (by default the current space) from the service, including which providers the space is currently registered with. -* `--space` The space to get information about. Defaults to the current space. -* `--json` Format as newline delimited JSON +- `--space` The space to get information about. Defaults to the current space. +- `--json` Format as newline delimited JSON ### `w3 delegation create ` Create a delegation to the passed audience for the given abilities with the _current_ space as the resource. -* `--can` A capability to delegate. To specify more than one capability, use this option more than once. -* `--name` Human readable name for the audience receiving the delegation. -* `--type` Type of the audience receiving the delegation, one of: device, app, service. -* `--output` Path of file to write the exported delegation data to. +- `--can` A capability to delegate. To specify more than one capability, use this option more than once. +- `--name` Human readable name for the audience receiving the delegation. +- `--type` Type of the audience receiving the delegation, one of: device, app, service. +- `--output` Path of file to write the exported delegation data to. ```bash # delegate space/info to did:key:z6MkrwtRceSo2bE6vAY4gi8xPNfNszSpvf8MpAHnxVfMYreN @@ -172,14 +174,13 @@ w3 delegation create did:key:z6MkrwtRceSo2bE6vAY4gi8xPNfNszSpvf8MpAHnxVfMYreN -- List delegations created by this agent for others. -* `--json` Format as newline delimited JSON - +- `--json` Format as newline delimited JSON ### `w3 delegation revoke ` Revoke a delegation by CID. -* `--proof` Name of a file containing the delegation and any additional proofs needed to prove authority to revoke +- `--proof` Name of a file containing the delegation and any additional proofs needed to prove authority to revoke ### `w3 proof add ` @@ -189,7 +190,13 @@ Add a proof delegated to this agent. The proof is a CAR encoded delegation to _t List proofs of delegated capabilities. Proofs are delegations with an audience matching the agent DID. -* `--json` Format as newline delimited JSON +- `--json` Format as newline delimited JSON + +### `w3 key create` + +Print a new key pair. Does not change your current signing key + +- `--json` Export as dag-json ### `w3 can space info ` @@ -203,10 +210,10 @@ Store a [CAR](https://ipld.io/specs/transport/car/carv1/) file to web3.storage. List CARs in the current space. -* `--json` Format as newline delimited JSON -* `--size` The desired number of results to return -* `--cursor` An opaque string included in a prior upload/list response that allows the service to provide the next "page" of results -* `--pre` If true, return the page of results preceding the cursor +- `--json` Format as newline delimited JSON +- `--size` The desired number of results to return +- `--cursor` An opaque string included in a prior upload/list response that allows the service to provide the next "page" of results +- `--pre` If true, return the page of results preceding the cursor ### `w3 can store rm ` @@ -220,17 +227,16 @@ Register an upload - a DAG with the given root data CID that is stored in the gi List uploads in the current space. -* `--json` Format as newline delimited JSON -* `--shards` Pretty print with shards in output -* `--size` The desired number of results to return -* `--cursor` An opaque string included in a prior upload/list response that allows the service to provide the next "page" of results -* `--pre` If true, return the page of results preceding the cursor +- `--json` Format as newline delimited JSON +- `--shards` Pretty print with shards in output +- `--size` The desired number of results to return +- `--cursor` An opaque string included in a prior upload/list response that allows the service to provide the next "page" of results +- `--pre` If true, return the page of results preceding the cursor ### `w3 can upload rm ` Remove an upload from the current space's upload list. Does not remove CAR from the store. - ## Environment Variables ### `W3_PRINCIPAL` @@ -240,6 +246,7 @@ Set the key `w3` should use to sign ucan invocations. By default `w3` will gener You can generate Ed25519 keys with [`ucan-key`](https://github.com/olizilla/ucan-key) e.g. `npx ucan-key ed` **Usage** + ```bash W3_PRINCIPAL=$(npx ucan-key ed --json | jq -r .key) W3_STORE_NAME="other" w3 whoami did:key:z6Mkf7bvSNgoXk67Ubhie8QMurN9E4yaCCGBzXow78zxnmuB diff --git a/bin.js b/bin.js index f99c307..c8b5fd6 100755 --- a/bin.js +++ b/bin.js @@ -24,6 +24,7 @@ import { whoami, usageReport, getPlan, + createKey, } from './index.js' import { storeAdd, @@ -294,6 +295,12 @@ cli .describe('Get filecoin information for given PieceCid.') .action(filecoinInfo) +cli + .command('key create') + .describe('Print a new key pair. Does not change your current signing key') + .option('--json', 'output as json') + .action(createKey) + // show help text if no command provided cli.command('help [cmd]', 'Show help text', { default: true }).action((cmd) => { try { diff --git a/index.js b/index.js index 6485648..8890d26 100644 --- a/index.js +++ b/index.js @@ -19,6 +19,7 @@ import { startOfLastMonth, } from './lib.js' import * as ucanto from '@ucanto/core' +import { ed25519, RSA } from '@ucanto/principal' import chalk from 'chalk' export * as Coupon from './coupon.js' export { Account, Space } @@ -121,8 +122,8 @@ export async function upload(firstPath, opts) { const uploadFn = opts?.car ? client.uploadCAR.bind(client, files[0]) : files.length === 1 && opts?.wrap === false - ? client.uploadFile.bind(client, files[0]) - : client.uploadDirectory.bind(client, files) + ? client.uploadFile.bind(client, files[0]) + : client.uploadDirectory.bind(client, files) const root = await uploadFn({ onShardStored: ({ cid, size, piece }) => { @@ -133,9 +134,14 @@ export async function upload(firstPath, opts) { ' └── ' )}Piece CID: ${piece}`, }) - spinner.start(`Storing ${Math.min(Math.round((totalSent / totalSize) * 100), 100)}%`) + spinner.start( + `Storing ${Math.min(Math.round((totalSent / totalSize) * 100), 100)}%` + ) } else { - spinner.text = `Storing ${Math.min(Math.round((totalSent / totalSize) * 100), 100)}%` + spinner.text = `Storing ${Math.min( + Math.round((totalSent / totalSize) * 100), + 100 + )}%` } opts?.json && opts?.verbose && @@ -482,7 +488,11 @@ export async function listProofs(opts) { console.log(chalk.dim(`# ${proof.cid.toString()}`)) console.log(`iss: ${chalk.cyanBright(proof.issuer.did())}`) if (proof.expiration !== Infinity) { - console.log(`exp: ${chalk.yellow(proof.expiration)} ${chalk.dim(` # expires ${ago(new Date(proof.expiration * 1000))}`)}`) + console.log( + `exp: ${chalk.yellow(proof.expiration)} ${chalk.dim( + ` # expires ${ago(new Date(proof.expiration * 1000))}` + )}` + ) } console.log('att:') for (const capability of proof.capabilities) { @@ -500,7 +510,13 @@ export async function listProofs(opts) { } console.log('') } - console.log(chalk.dim(`# ${proofs.length} proof${proofs.length === 1 ? '' : 's'} for ${client.agent.did()}`)) + console.log( + chalk.dim( + `# ${proofs.length} proof${ + proofs.length === 1 ? '' : 's' + } for ${client.agent.did()}` + ) + ) } } @@ -572,3 +588,17 @@ async function* getSpaceUsageReports(client, period) { } } } + +/** + * @param {{ json: boolean }} options + */ +export async function createKey({ json }) { + const signer = await ed25519.generate() + const key = ed25519.format(signer) + if (json) { + console.log(dagJSON.stringify(signer.toArchive())) + } else { + console.log(`# ${signer.did()}`) + console.log(`${key}`) + } +} diff --git a/test/bin.spec.js b/test/bin.spec.js index de6246a..e76ca35 100644 --- a/test/bin.spec.js +++ b/test/bin.spec.js @@ -400,7 +400,9 @@ export const testSpace = { }), 'w3 space use': test(async (assert, context) => { - const spaceDID = await loginAndCreateSpace(context, { env: context.env.alice }) + const spaceDID = await loginAndCreateSpace(context, { + env: context.env.alice, + }) const listDefault = await w3 .args(['space', 'ls']) @@ -1168,6 +1170,14 @@ export const testPlan = { }), } +export const testKey = { + 'w3 key create': test(async (assert) => { + const res = await w3.args(['key', 'create', '--json']).join() + const key = ED25519.from(dagJSON.parse(res.output)) + assert.ok(key.did().startsWith('did:key')) + }), +} + /** * @param {Test.Context} context * @param {object} options @@ -1217,11 +1227,7 @@ export const selectPlan = async ( */ export const createSpace = async ( context, - { - customer = 'alice@web.mail', - name = 'home', - env = context.env.alice - } = {} + { customer = 'alice@web.mail', name = 'home', env = context.env.alice } = {} ) => { const { output } = await w3 .args([ @@ -1256,7 +1262,7 @@ export const loginAndCreateSpace = async ( customer = email, name = 'home', plan = 'did:web:free.web3.storage', - env = context.env.alice + env = context.env.alice, } = {} ) => { await login(context, { email, env }) From 84b5b6ba00b88928f455adedb5659919af28af99 Mon Sep 17 00:00:00 2001 From: Oli Evans Date: Thu, 11 Jan 2024 11:36:54 +0000 Subject: [PATCH 2/3] chore: Apply suggestions from code review Co-authored-by: Alan Shaw --- README.md | 3 +-- bin.js | 2 +- index.js | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 32cc4f4..5c3ab5d 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ w3 up recipies.txt - [`w3 proof add`](#w3-proof-add-proofucan) - [`w3 proof ls`](#w3-proof-ls) - Key management -- [`w3 key create`](#w3-key-create) + - [`w3 key create`](#w3-key-create) - Advanced usage - [`w3 can space info`](#w3-can-space-info-did) coming soon! - [`w3 can space recover`](#w3-can-space-recover-email) coming soon! @@ -135,7 +135,6 @@ register the space with. - `--email` The email address of the account to associate this space with. - `--provider` The storage provider to associate with this space. - > By registering your w3up beta Space with [web3.storage](http://web3.storage/), you agree to the w3up beta [Terms of Service](https://console.web3.storage/terms). Until the beta period is over and this migration occurs, uploads to w3up will not appear in your web3.storage account (and vice versa), even if you register with the same email. ### `w3 space use ` diff --git a/bin.js b/bin.js index c8b5fd6..6ed89d1 100755 --- a/bin.js +++ b/bin.js @@ -297,7 +297,7 @@ cli cli .command('key create') - .describe('Print a new key pair. Does not change your current signing key') + .describe('Generate and print a new ed25519 key pair. Does not change your current signing key.') .option('--json', 'output as json') .action(createKey) diff --git a/index.js b/index.js index 8890d26..89fcc98 100644 --- a/index.js +++ b/index.js @@ -599,6 +599,6 @@ export async function createKey({ json }) { console.log(dagJSON.stringify(signer.toArchive())) } else { console.log(`# ${signer.did()}`) - console.log(`${key}`) + console.log(key) } } From d00a9339a8aa5d323421113e2144d300dab862cf Mon Sep 17 00:00:00 2001 From: Oli Evans Date: Fri, 12 Jan 2024 14:42:20 +0000 Subject: [PATCH 3/3] chore: simplify json output to be like ucan-key License: MIT Signed-off-by: Oli Evans --- index.js | 4 ++-- test/bin.spec.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 89fcc98..da259fb 100644 --- a/index.js +++ b/index.js @@ -19,7 +19,7 @@ import { startOfLastMonth, } from './lib.js' import * as ucanto from '@ucanto/core' -import { ed25519, RSA } from '@ucanto/principal' +import { ed25519 } from '@ucanto/principal' import chalk from 'chalk' export * as Coupon from './coupon.js' export { Account, Space } @@ -596,7 +596,7 @@ export async function createKey({ json }) { const signer = await ed25519.generate() const key = ed25519.format(signer) if (json) { - console.log(dagJSON.stringify(signer.toArchive())) + console.log(JSON.stringify({ did: signer.did(), key }, null, 2)) } else { console.log(`# ${signer.did()}`) console.log(key) diff --git a/test/bin.spec.js b/test/bin.spec.js index e76ca35..e498849 100644 --- a/test/bin.spec.js +++ b/test/bin.spec.js @@ -1173,7 +1173,7 @@ export const testPlan = { export const testKey = { 'w3 key create': test(async (assert) => { const res = await w3.args(['key', 'create', '--json']).join() - const key = ED25519.from(dagJSON.parse(res.output)) + const key = ED25519.parse(JSON.parse(res.output).key) assert.ok(key.did().startsWith('did:key')) }), }