Skip to content

Commit

Permalink
[gh-2381] revise account sign command and add account verify command (#…
Browse files Browse the repository at this point in the history
…2566)

* [gh-2381] add msg prefix to the account sign command.

* [gh-2381] modify the sign command output as msg and auth payload.

* [gh-2381] modify sign command for signing.

* [gh-2381] add verify command.

* [gh-2381] revise the sign and verify process.

* [gh-2381] revise account verify command and add tests.

* [gh-2381] follow #2580 to add account sign command.

* [gh-2381] fix some issues of the account sign command.

* [gh-2381] revise account verify command.

* [gh-2381] revise the integ test.

* [gh-2381] fix issues arisen from comments.

* [gh-2381] fix cmd.feature.

* [gh-2381] fix output.

* [gh-2381] fix lint issues.

* [gh-2381] fix tests.

* [gh-2381] add assert to test and parsed signature struct.

* [gh-2381] format the code.

* [gh-2381] revise the signature hex and verify command.

* [gh-2381] fix the issue of verification.

---------

Co-authored-by: Feliciss <10203-feliciss@users.noreply.0xacab.org>
  • Loading branch information
feliciss and Feliciss authored Sep 8, 2024
1 parent 69e03e7 commit ac768f3
Show file tree
Hide file tree
Showing 11 changed files with 167 additions and 47 deletions.
17 changes: 0 additions & 17 deletions crates/rooch-rpc-api/src/jsonrpc_types/account_sign_view.rs

This file was deleted.

2 changes: 0 additions & 2 deletions crates/rooch-rpc-api/src/jsonrpc_types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,3 @@ pub use rpc_options::*;
pub use state_view::*;
pub use str_view::*;
pub use transaction_argument_view::*;

pub mod account_sign_view;
47 changes: 44 additions & 3 deletions crates/rooch-types/src/framework/auth_payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ use moveos_types::{
h256::{sha2_256_of, H256},
state::{MoveStructState, MoveStructType},
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::io;

pub const MODULE_NAME: &IdentStr = ident_str!("auth_payload");

/// The original message prefix of the Bitcoin wallet includes the length of the message `x18`
/// We remove the length because the bitcoin consensus codec serialization format already contains the length information
const MESSAGE_INFO_PREFIX: &[u8] = b"Bitcoin Signed Message:\n";
const MESSAGE_INFO: &[u8] = b"Rooch Transaction:\n";
pub const MESSAGE_INFO_PREFIX: &[u8] = b"Bitcoin Signed Message:\n";
pub const MESSAGE_INFO: &[u8] = b"Rooch Transaction:\n";

const TX_HASH_HEX_LENGTH: usize = 64;

Expand Down Expand Up @@ -54,10 +55,24 @@ impl SignData {
}
}

pub fn new_without_tx_hash(
message_prefix: Vec<u8>,
message_info_without_tx_hash: Vec<u8>,
) -> Self {
SignData {
message_prefix,
message_info: message_info_without_tx_hash,
}
}

pub fn new_with_default(tx_data: &RoochTransactionData) -> Self {
Self::new(MESSAGE_INFO_PREFIX.to_vec(), MESSAGE_INFO.to_vec(), tx_data)
}

pub fn new_without_tx_hash_with_default() -> Self {
Self::new_without_tx_hash(MESSAGE_INFO_PREFIX.to_vec(), MESSAGE_INFO.to_vec())
}

pub fn encode(&self) -> Vec<u8> {
let mut data = Vec::new();
self.consensus_encode(&mut data)
Expand Down Expand Up @@ -94,7 +109,7 @@ impl Decodable for SignData {
}
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct AuthPayload {
// Message signature
pub signature: Vec<u8>,
Expand Down Expand Up @@ -149,6 +164,21 @@ impl AuthPayload {
}
}

pub fn new_without_tx_hash(
sign_data: SignData,
signature: Signature,
bitcoin_address: String,
) -> Self {
debug_assert_eq!(signature.scheme(), SignatureScheme::Secp256k1);
AuthPayload {
signature: signature.signature_bytes().to_vec(),
message_prefix: sign_data.message_prefix,
message_info: sign_data.message_info,
public_key: signature.public_key_bytes().to_vec(),
from_address: bitcoin_address.into_bytes(),
}
}

pub fn verify(&self, tx_data: &RoochTransactionData) -> Result<()> {
let pk = Secp256k1PublicKey::from_bytes(&self.public_key)?;
let sign_data = SignData::new(
Expand All @@ -163,6 +193,17 @@ impl AuthPayload {
Ok(())
}

pub fn verify_without_tx_hash(&self) -> Result<()> {
let pk = Secp256k1PublicKey::from_bytes(&self.public_key)?;
let sign_data =
SignData::new_without_tx_hash(self.message_prefix.clone(), self.message_info.clone());
let message = sign_data.encode();
let message_hash = sha2_256_of(&message).0.to_vec();
let signature = Secp256k1Signature::from_bytes(&self.signature)?;
pk.verify_with_hash::<Sha256>(&message_hash, &signature)?;
Ok(())
}

pub fn from_address(&self) -> Result<String> {
Ok(String::from_utf8(self.from_address.to_vec())?)
}
Expand Down
1 change: 1 addition & 0 deletions crates/rooch-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub mod nursery;
pub mod repair;
pub mod rooch_key;
pub mod rooch_network;
pub mod rooch_signature;
pub mod sequencer;
pub mod service_status;
pub mod test_utils;
Expand Down
27 changes: 27 additions & 0 deletions crates/rooch-types/src/rooch_signature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) RoochNetwork
// SPDX-License-Identifier: Apache-2.0

use crate::crypto::Signature;
use fastcrypto::traits::ToFromBytes;
use serde::{Deserialize, Serialize};

// Parsed Rooch Signature, either Ed25519RoochSignature or Secp256k1RoochSignature
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
pub struct ParsedSignature(Signature);

impl ParsedSignature {
pub fn into_inner(self) -> Signature {
self.0
}

pub fn from_signature(signature: Signature) -> Self {
Self(signature)
}

pub fn parse(s: &str) -> anyhow::Result<Self, anyhow::Error> {
let signature_bytes = hex::decode(s)?;
Ok(Self::from_signature(Signature::from_bytes(
&signature_bytes,
)?))
}
}
7 changes: 7 additions & 0 deletions crates/rooch-types/src/transaction/rooch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ impl RoochTransactionData {
bcs::to_bytes(self).expect("encode transaction should success")
}

pub fn decode(bytes: &[u8]) -> Result<Self>
where
Self: std::marker::Sized,
{
bcs::from_bytes::<Self>(bytes).map_err(Into::into)
}

pub fn tx_hash(&self) -> H256 {
moveos_types::h256::sha3_256_of(self.encode().as_slice())
}
Expand Down
1 change: 1 addition & 0 deletions crates/rooch/src/commands/account/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ pub mod nullify;
pub mod sign;
pub mod switch;
pub mod transfer;
pub mod verify;
50 changes: 26 additions & 24 deletions crates/rooch/src/commands/account/commands/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,60 @@
use crate::cli_types::{CommandAction, WalletContextOptions};
use async_trait::async_trait;
use clap::Parser;
use hex::ToHex;
use moveos_types::state::MoveState;
use rooch_key::keystore::account_keystore::AccountKeystore;
use rooch_rpc_api::jsonrpc_types::account_sign_view::AccountSignView;
use rooch_types::{
address::ParsedAddress,
error::{RoochError, RoochResult},
error::RoochResult,
framework::auth_payload::{SignData, MESSAGE_INFO_PREFIX},
};

/// Sign an msg with current account private key (sign_hashed)
///
/// This operation must be specified with -a or
/// --address to export only one address with a private key.
/// Sign a message with a parsed address
#[derive(Debug, Parser)]
pub struct SignCommand {
// An address to be used
#[clap(short = 'a', long = "address", value_parser=ParsedAddress::parse, default_value = "")]
address: ParsedAddress,

/// A message to be signed
#[clap(short = 'm', long)]
message: String,

#[clap(flatten)]
pub context_options: WalletContextOptions,

/// Return command outputs in json format
#[clap(long, default_value = "false")]
json: bool,

/// Msg command will sign
#[clap(long, default_value = "")]
msg: String,
}

#[async_trait]
impl CommandAction<Option<AccountSignView>> for SignCommand {
async fn execute(self) -> RoochResult<Option<AccountSignView>> {
impl CommandAction<Option<String>> for SignCommand {
async fn execute(self) -> RoochResult<Option<String>> {
let context = self.context_options.build_require_password()?;
let password = context.get_password();

let mapping = context.address_mapping();
let addrss = self.address.into_rooch_address(&mapping).map_err(|e| {
RoochError::CommandArgumentError(format!("Invalid Rooch address String: {}", e))
})?;
let rooch_address = self.address.into_rooch_address(&mapping)?;

let sign_data =
SignData::new_without_tx_hash(MESSAGE_INFO_PREFIX.to_vec(), self.message.to_bytes());
let encoded_sign_data = sign_data.encode();

let signature =
context
.keystore
.sign_hashed(&addrss, &self.msg.clone().into_bytes(), password)?;
.sign_hashed(&rooch_address, &encoded_sign_data, password)?;

let signature_bytes = signature.as_ref();
let signature_hex = hex::encode(signature_bytes);

if self.json {
Ok(Some(AccountSignView::new(
self.msg.clone(),
signature.encode_hex(),
)))
Ok(Some(signature_hex))
} else {
println!("Msg you input : {}", &self.msg);
println!("Signature : {}", signature.encode_hex::<String>());
println!(
"Sign message succeeded with the signatue {:?}",
signature_hex
);
Ok(None)
}
}
Expand Down
53 changes: 53 additions & 0 deletions crates/rooch/src/commands/account/commands/verify.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) RoochNetwork
// SPDX-License-Identifier: Apache-2.0

use crate::cli_types::{CommandAction, WalletContextOptions};
use async_trait::async_trait;
use clap::Parser;
use moveos_types::state::MoveState;
use rooch_types::{
crypto::RoochSignature,
error::RoochResult,
framework::auth_payload::{SignData, MESSAGE_INFO_PREFIX},
rooch_signature::ParsedSignature,
};

/// Verify a signature
#[derive(Debug, Parser)]
pub struct VerifyCommand {
/// A signature for verify
#[clap(short = 's', long, value_parser=ParsedSignature::parse)]
signature: ParsedSignature,

/// An original message to be verified
#[clap(short = 'm', long)]
message: String,

#[clap(flatten)]
pub context_options: WalletContextOptions,

/// Return command outputs in json format
#[clap(long, default_value = "false")]
json: bool,
}

#[async_trait]
impl CommandAction<Option<bool>> for VerifyCommand {
async fn execute(self) -> RoochResult<Option<bool>> {
let sign_data =
SignData::new_without_tx_hash(MESSAGE_INFO_PREFIX.to_vec(), self.message.to_bytes());
let encoded_sign_data = sign_data.encode();
let verify_result = self
.signature
.into_inner()
.verify(&encoded_sign_data)
.is_ok();

if self.json {
Ok(Some(verify_result))
} else {
println!("Verification result: {}", verify_result);
Ok(None)
}
}
}
4 changes: 3 additions & 1 deletion crates/rooch/src/commands/account/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use async_trait::async_trait;
use commands::{
balance::BalanceCommand, create::CreateCommand, create_multisign::CreateMultisignCommand,
export::ExportCommand, import::ImportCommand, list::ListCommand, nullify::NullifyCommand,
sign::SignCommand, switch::SwitchCommand, transfer::TransferCommand,
sign::SignCommand, switch::SwitchCommand, transfer::TransferCommand, verify::VerifyCommand,
};
use rooch_types::error::RoochResult;
use std::path::PathBuf;
Expand Down Expand Up @@ -38,6 +38,7 @@ impl CommandAction<String> for Account {
AccountCommand::Export(export) => export.execute_serialized().await,
AccountCommand::Import(import) => import.execute_serialized().await,
AccountCommand::Sign(sign) => sign.execute_serialized().await,
AccountCommand::Verify(verify) => verify.execute_serialized().await,
}
}
}
Expand All @@ -55,4 +56,5 @@ pub enum AccountCommand {
Export(ExportCommand),
Import(ImportCommand),
Sign(SignCommand),
Verify(VerifyCommand),
}
5 changes: 5 additions & 0 deletions crates/testsuite/features/cmd.feature
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ Feature: Rooch CLI integration tests

Then cmd: "account create"
Then cmd: "account list --json"
# account sign and verify
Then cmd: "account sign -a {{$.account[-1].account0.address}} -m 'empty' --json"
Then cmd: "account verify -s {{$.account[-1]}} -m 'empty' --json"
Then assert: "{{$.account[-1]}} == true"
Then cmd: "account list --json"
Then cmd: "account export"
Then cmd: "account export -a {{$.account[-1].account0.address}} --json"
# use bitcoin_address
Expand Down

0 comments on commit ac768f3

Please sign in to comment.