From 631b8b47c5b4241a29503267d732e90efb0ef5c2 Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 13 May 2024 18:13:03 +0200 Subject: [PATCH] Updates for test-cli before publishing and to work nicely with v0.0.12 (#830) * Update readme for test-cli * Updates to test-cli for working well with v0.0.12 * Client register command should return the verfiying key * Update test-cli to display verifying key on registration * Update readme for test-cli --- crates/client/src/client.rs | 9 +++-- crates/client/src/errors.rs | 2 + crates/test-cli/README.md | 77 ++++++++++++++++++++----------------- crates/test-cli/src/main.rs | 69 +++++++++++++++++---------------- 4 files changed, 84 insertions(+), 73 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index ef586d388..f34d31767 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -81,7 +81,8 @@ pub async fn register( key_visibility: KeyVisibility, programs_data: BoundedVec, x25519_secret_key: Option, -) -> Result<(RegisteredInfo, Option>), ClientError> { +) -> Result<([u8; VERIFYING_KEY_LENGTH], RegisteredInfo, Option>), ClientError> +{ // Send register transaction put_register_request_on_chain( api, @@ -130,12 +131,14 @@ pub async fn register( EventsClient::new(api.clone()).at(block_hash.ok_or(ClientError::BlockHash)?).await?; let registered_event = events.find::(); for event in registered_event.flatten() { + // check if the event belongs to this user if event.0 == account_id { let registered_query = entropy::storage().registry().registered(&event.1); let registered_status = query_chain(api, rpc, registered_query, block_hash).await?; if let Some(status) = registered_status { - // check if the event belongs to this user - return Ok((status, keyshare_option)); + let verifying_key = + event.1 .0.try_into().map_err(|_| ClientError::BadVerifyingKeyLength)?; + return Ok((verifying_key, status, keyshare_option)); } } } diff --git a/crates/client/src/errors.rs b/crates/client/src/errors.rs index eea71d2c0..7c16d28e2 100644 --- a/crates/client/src/errors.rs +++ b/crates/client/src/errors.rs @@ -100,4 +100,6 @@ pub enum ClientError { SubgroupFetch, #[error("Cannot query whether validator is synced")] CannotQuerySynced, + #[error("Verifying key has incorrect length")] + BadVerifyingKeyLength, } diff --git a/crates/test-cli/README.md b/crates/test-cli/README.md index 455065c2c..7965812b0 100644 --- a/crates/test-cli/README.md +++ b/crates/test-cli/README.md @@ -28,6 +28,10 @@ When using the local docker compose setup, be aware you need to set the TSS host 127.0.0.1 bob-tss-server ``` +## Installation + +`cargo install entropy-test-cli` + ## Usage ### Account names @@ -47,70 +51,72 @@ however that account names are case sensitive, so `//Alice` and `//alice` are di To see usage information you can run the `help` command: -`cargo run -p test-cli -- help` +`entropy-test-cli -- help` You can also display help for a specific command: -`cargo run -p test-cli -- help register` +`entropy-test-cli -- help register` ### Status To see if you have access to a successfully configured deployment you can try the `status` command which will list the currently registered entropy accounts and stored programs: -`cargo run -p test-cli -- status` +`entropy-test-cli -- status` ### Register To register an entropy account you need three things: -- An Entropy chain account name which we will call the 'signature request account'. This must be funded in - order to submit the register transaction. On the local (docker compose) setup you can use one of the +- An Entropy chain account name which we will call the 'program modification account'. This must be funded + in order to submit the register transaction. On the local (docker compose) setup you can use one of the [pre-endowed accounts](https://github.com/entropyxyz/entropy-core/blob/master/node/cli/src/endowed_accounts.rs), - for example `Alice`. Note however that the accounts `Dave`, `Eve` and `Ferdie` are also pre-registered, - which means that you cannot register them again. If you are using a network deployment you will need - to obtain some tokens by transferring them from the root account. -- An Entropy chain account name which we will call the 'program modification account'. This does not - need to be funded in order to register, only if you want to change which program(s) you are using - later. + for example `Alice`. - One or more programs, which define the conditions under which a given message will be signed by the Entropy network. The test-cli `register` command takes programs as either the hex-encoded hash of an existing program on chain, or the local path to a `.wasm` file containing the compiled - program. The [`testing-utils`](https://github.com/entropyxyz/entropy-core/tree/master/crates/testing-utils) - crate contains some ready to use compiled programs, the simplest of which is [`example_noop.wasm`](https://github.com/entropyxyz/entropy-core/blob/master/crates/testing-utils/example_noop.wasm) which will simply sign all messages. See the - [`programs` crate](https://github.com/entropyxyz/programs) for more example programs as well as - instructions on how to write and build your own programs. - -You also need to decide which ['access mode' or 'key visibility'](https://entropy-docs.vercel.app/KeyVisibility) -you want to register with: private, permissioned or public. If you are not sure, 'permissioned' is the -simplest 'vanilla' access mode. + program. + - The [`device-key-proxy`](https://github.com/entropyxyz/programs/blob/master/examples/device-key-proxy/src/lib.rs) + program is always available with the zero hash: `0000000000000000000000000000000000000000000000000000000000000000`. + - The [`testing-utils`](https://github.com/entropyxyz/entropy-core/tree/master/crates/testing-utils) + crate contains some ready to use compiled programs, the simplest of which is + [`template_barebones.wasm`](https://github.com/entropyxyz/entropy-core/blob/master/crates/testing-utils/template_barebones.wasm) + which allow you to sign any message which is more than 10 bytes long. + - See the [`programs` crate](https://github.com/entropyxyz/programs) for more example programs as well as + instructions on how to write and build your own programs. + +You also need to decide which ['access mode' or 'key visibility'](https://docs.entropy.xyz/AccessModes) +you want to register with: private or public. If you are not sure, 'public' is the simplest 'vanilla' +access mode. For example, to register with `//Alice` as the signature request account and `//Bob` as the program -modification account, in permissioned access mode, using the `example_noop` program: +modification account, in permissioned access mode, using the `template_barebones` program: -`cargo run -p test-cli -- register Alice Bob permissioned ./crates/testing-utils/example_noop.wasm` +`entropy-test-cli register Alice public template_barebones.wasm` -Example of registering in private access mode, with a program given as a hash of an existing -program: +Example of registering in private access mode, with two programs, one given as a binary file and one +given as a hash of an existing program: -`cargo run -p test-cli -- register Alice Bob private my-program.wasm 3b3993c957ed9342cbb011eb9029c53fb253345114eff7da5951e98a41ba5ad5` +`entropy-test-cli register Alice private my-program.wasm 3b3993c957ed9342cbb011eb9029c53fb253345114eff7da5951e98a41ba5ad5` When registering with private access mode, a keyshare file will be written to the directory where you run the command. You must make subsequent `sign` commands in the same directory. -Once you have successfully registered you can run the `status` command again and you should see the -account you registered. The 'verifying key' field is the public secp256k1 key of the distributed -keypair used to sign messages from the Entropy account. +If registration was successful you will see the verifying key of your account, which is the public +secp256k1 key of your distributed keypair. You will need this in order to specify the account when +requesting to sign a message. If you run the `status` command again and you should see the account +you registered. ### Sign -The `sign` command takes a signature request 'account name' and a message to be signed. +The `sign` command takes the verifying key of the account, given as hex, and a message to be signed, +given as a UTF-8 string. -`cargo run -p test-cli -- sign Alice 'My message to sign'` +`entropy-test-cli -- sign 039fa2a16982fa6176e3fa9ae8dc408386ff040bf91196d3ec0aa981e5ba3fc1bb 'My message to sign'` If the program you have set takes additional auxiliary data, you can provided it as a hex encoded string: -`cargo run -p test-cli -- sign Alice 'My message to sign' deadbeef1234` +`entropy-test-cli -- sign 039fa2a16982fa6176e3fa9ae8dc408386ff040bf91196d3ec0aa981e5ba3fc1bb 'My message to sign' deadbeef1234` If signing is successful, a [`RecoverableSignature`](https://docs.rs/synedrion/latest/synedrion/struct.RecoverableSignature.html) object will be displayed containing the 64 byte secp256k1 signature encoded as hex, as well as a [`RecoveryId`](https://docs.rs/synedrion/latest/synedrion/ecdsa/struct.RecoveryId.html). @@ -123,16 +129,15 @@ a program you can use the `store-program` command. You need to give the account which will store the program, and the path to a program binary file you wish to store, for example: -`cargo run -p test-cli -- store-program Alice -./crates/testing-utils/example_barebones_with_auxilary.wasm` +`entropy-test-cli store-program Alice ./crates/testing-utils/example_barebones_with_auxilary.wasm` ### Update programs The `update-programs` command is used to change the programs associated with a registered Entropy -account. It takes the 'account name' of the signature request account, and the program modification -account, and a list of programs to evaluate when signing. Programs may be given as either the path -to a .wasm binary file or hashes of existing programs. +account. It takes the signature verifying key, and the program modification account, and a list of +programs to evaluate when signing. Programs may be given as either the path to a .wasm binary file +or hashes of existing programs. -`cargo run -p test-cli -- update-programs Alice Bob my-new-program.wasm` +`entropy-test-cli update-programs 039fa2a16982fa6176e3fa9ae8dc408386ff040bf91196d3ec0aa981e5ba3fc1bb Alice my-new-program.wasm` Note that the program modification account must be funded for this to work. diff --git a/crates/test-cli/src/main.rs b/crates/test-cli/src/main.rs index ad36aca1d..a62687a31 100644 --- a/crates/test-cli/src/main.rs +++ b/crates/test-cli/src/main.rs @@ -69,11 +69,8 @@ struct Cli { enum CliCommand { /// Register with Entropy and create keyshares Register { - /// A name from which to generate a signature request keypair, eg: "Alice" - /// - /// Optionally may be preceeded with "//", eg: "//Alice" - signature_request_account_name: String, /// A name from which to generate a program modification keypair, eg: "Bob" + /// This is used to send the register extrinsic and so it must be funded /// /// Optionally may be preceeded with "//" eg: "//Bob" program_account_name: String, @@ -95,16 +92,18 @@ enum CliCommand { }, /// Ask the network to sign a given message Sign { - /// A name from which to generate a keypair, eg: "Alice" - /// - /// Optionally may be preceeded with "//", eg: "//Alice" - user_account_name: String, /// The verifying key of the account to sign with, given as hex signature_verifying_key: String, /// The message to be signed message: String, /// Optional auxiliary data passed to the program, given as hex auxilary_data: Option, + /// A name from which to generate a keypair, eg: "Alice" + /// This is only needed when using private mode. + /// + /// Optionally may be preceeded with "//", eg: "//Alice" + #[arg(short, long)] + program_account_name: Option, }, /// Update the program for a particular account UpdatePrograms { @@ -194,16 +193,7 @@ async fn run_command() -> anyhow::Result { let rpc = get_rpc(&endpoint_addr).await?; match cli.command { - CliCommand::Register { - signature_request_account_name, - program_account_name, - key_visibility, - programs, - } => { - let signature_request_keypair: sr25519::Pair = - SeedString::new(signature_request_account_name).try_into()?; - println!("Signature request account: {}", signature_request_keypair.public()); - + CliCommand::Register { program_account_name, key_visibility, programs } => { let program_keypair: sr25519::Pair = SeedString::new(program_account_name).try_into()?; let program_account = SubxtAccountId32(program_keypair.public().0); @@ -211,7 +201,7 @@ async fn run_command() -> anyhow::Result { let (key_visibility_converted, x25519_secret) = match key_visibility { Visibility::Private => { - let x25519_secret = derive_x25519_static_secret(&signature_request_keypair); + let x25519_secret = derive_x25519_static_secret(&program_keypair); let x25519_public = x25519_dalek::PublicKey::from(&x25519_secret); (KeyVisibility::Private(x25519_public.to_bytes()), Some(x25519_secret)) }, @@ -225,10 +215,10 @@ async fn run_command() -> anyhow::Result { ); } - let (registered_info, keyshare_option) = register( + let (verifying_key, registered_info, keyshare_option) = register( &api, &rpc, - signature_request_keypair.clone(), + program_keypair.clone(), program_account, key_visibility_converted, BoundedVec(programs_info), @@ -238,31 +228,42 @@ async fn run_command() -> anyhow::Result { // If we got a keyshare, write it to a file if let Some(keyshare) = keyshare_option { - KeyShareFile::new(signature_request_keypair.public()).write(keyshare)?; + let verifying_key = + keyshare.verifying_key().to_encoded_point(true).as_bytes().to_vec(); + KeyShareFile::new(&verifying_key).write(keyshare)?; } - Ok(format!("{:?}", registered_info)) + Ok(format!("Verfiying key: {},\n{:?}", hex::encode(verifying_key), registered_info)) }, - CliCommand::Sign { user_account_name, signature_verifying_key, message, auxilary_data } => { - let user_keypair: sr25519::Pair = SeedString::new(user_account_name).try_into()?; + CliCommand::Sign { + signature_verifying_key, + message, + auxilary_data, + program_account_name, + } => { + // If an account name is not provided, use the signature verifying key + let user_keypair: sr25519::Pair = SeedString::new( + program_account_name.unwrap_or_else(|| signature_verifying_key.clone()), + ) + .try_into()?; println!("User account: {}", user_keypair.public()); let auxilary_data = if let Some(data) = auxilary_data { Some(hex::decode(data)?) } else { None }; + let signature_verifying_key: [u8; VERIFYING_KEY_LENGTH] = + hex::decode(signature_verifying_key)? + .try_into() + .map_err(|_| anyhow!("Verifying key must be 33 bytes"))?; + // If we have a keyshare file for this account, get it - let private_keyshare = KeyShareFile::new(user_keypair.public()).read().ok(); + let private_keyshare = KeyShareFile::new(&signature_verifying_key.to_vec()).read().ok(); let private_details = private_keyshare.map(|keyshare| { let x25519_secret = derive_x25519_static_secret(&user_keypair); (keyshare, x25519_secret) }); - let signature_verifying_key: [u8; VERIFYING_KEY_LENGTH] = - hex::decode(signature_verifying_key)? - .try_into() - .map_err(|_| anyhow!("Verifying key must be 33 bytes"))?; - let recoverable_signature = sign( &api, &rpc, @@ -382,7 +383,7 @@ async fn run_command() -> anyhow::Result { ); for (hash, program_info) in programs { println!( - "{} {} {:>11} {:>14} {} {}", + "{} {} {:>11} {:>14} {:<13} {}", hash, program_info.deployer, program_info.ref_counter, @@ -421,8 +422,8 @@ impl TryFrom for sr25519::Pair { struct KeyShareFile(String); impl KeyShareFile { - fn new(public_key: sr25519::Public) -> Self { - Self(format!("keyshare-{}", hex::encode(public_key.0))) + fn new(verifying_key: &Vec) -> Self { + Self(format!("keyshare-{}", hex::encode(verifying_key))) } fn read(&self) -> anyhow::Result> {