Skip to content

Commit

Permalink
Updates for test-cli before publishing and to work nicely with v0.0.12 (
Browse files Browse the repository at this point in the history
#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
  • Loading branch information
ameba23 authored May 13, 2024
1 parent 0f89af7 commit 631b8b4
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 73 deletions.
9 changes: 6 additions & 3 deletions crates/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ pub async fn register(
key_visibility: KeyVisibility,
programs_data: BoundedVec<ProgramInstance>,
x25519_secret_key: Option<StaticSecret>,
) -> Result<(RegisteredInfo, Option<KeyShare<KeyParams>>), ClientError> {
) -> Result<([u8; VERIFYING_KEY_LENGTH], RegisteredInfo, Option<KeyShare<KeyParams>>), ClientError>
{
// Send register transaction
put_register_request_on_chain(
api,
Expand Down Expand Up @@ -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::<entropy::registry::events::AccountRegistered>();
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));
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions crates/client/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,6 @@ pub enum ClientError {
SubgroupFetch,
#[error("Cannot query whether validator is synced")]
CannotQuerySynced,
#[error("Verifying key has incorrect length")]
BadVerifyingKeyLength,
}
77 changes: 41 additions & 36 deletions crates/test-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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).
Expand All @@ -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.
69 changes: 35 additions & 34 deletions crates/test-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<String>,
/// 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<String>,
},
/// Update the program for a particular account
UpdatePrograms {
Expand Down Expand Up @@ -194,24 +193,15 @@ async fn run_command() -> anyhow::Result<String> {
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);
println!("Program account: {}", program_keypair.public());

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))
},
Expand All @@ -225,10 +215,10 @@ async fn run_command() -> anyhow::Result<String> {
);
}

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),
Expand All @@ -238,31 +228,42 @@ async fn run_command() -> anyhow::Result<String> {

// 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,
Expand Down Expand Up @@ -382,7 +383,7 @@ async fn run_command() -> anyhow::Result<String> {
);
for (hash, program_info) in programs {
println!(
"{} {} {:>11} {:>14} {} {}",
"{} {} {:>11} {:>14} {:<13} {}",
hash,
program_info.deployer,
program_info.ref_counter,
Expand Down Expand Up @@ -421,8 +422,8 @@ impl TryFrom<SeedString> 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<u8>) -> Self {
Self(format!("keyshare-{}", hex::encode(verifying_key)))
}

fn read(&self) -> anyhow::Result<KeyShare<KeyParams>> {
Expand Down

0 comments on commit 631b8b4

Please sign in to comment.