Skip to content

Commit

Permalink
added offline sign utils, improve signature serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
Fraccaman committed Aug 26, 2024
1 parent 679dd3b commit 6c3acac
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 8 deletions.
60 changes: 60 additions & 0 deletions crates/apps_lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2393,6 +2393,7 @@ pub mod cmds {
InitGenesisEstablishedAccount(InitGenesisEstablishedAccount),
InitGenesisValidator(InitGenesisValidator),
PkToTmAddress(PkToTmAddress),
SignOffline(SignOffline),
DefaultBaseDir(DefaultBaseDir),
EpochSleep(EpochSleep),
ValidateGenesisTemplates(ValidateGenesisTemplates),
Expand Down Expand Up @@ -2421,6 +2422,8 @@ pub mod cmds {
SubCmd::parse(matches).map(Self::InitGenesisValidator);
let pk_to_tm_address =
SubCmd::parse(matches).map(Self::PkToTmAddress);
let sign_offline =
SubCmd::parse(matches).map(Self::SignOffline);
let default_base_dir =
SubCmd::parse(matches).map(Self::DefaultBaseDir);
let epoch_sleep = SubCmd::parse(matches).map(Self::EpochSleep);
Expand All @@ -2443,6 +2446,7 @@ pub mod cmds {
.or(validate_genesis_templates)
.or(genesis_tx)
.or(parse_migrations_json)
.or(sign_offline)
})
}

Expand All @@ -2457,6 +2461,7 @@ pub mod cmds {
.subcommand(InitGenesisEstablishedAccount::def())
.subcommand(InitGenesisValidator::def())
.subcommand(PkToTmAddress::def())
.subcommand(SignOffline::def())
.subcommand(DefaultBaseDir::def())
.subcommand(EpochSleep::def())
.subcommand(ValidateGenesisTemplates::def())
Expand Down Expand Up @@ -3124,6 +3129,25 @@ pub mod cmds {
}
}

#[derive(Clone, Debug)]
pub struct SignOffline(pub args::SignOffline);

impl SubCmd for SignOffline {
const CMD: &'static str = "sign-offline";

fn parse(matches: &ArgMatches) -> Option<Self> {
matches
.subcommand_matches(Self::CMD)
.map(|matches| Self(args::SignOffline::parse(matches)))
}

fn def() -> App {
App::new(Self::CMD)
.about(wrap!("Offlne sign a transaction."))
.add_args::<args::SignOffline>()
}
}

#[derive(Clone, Debug)]
pub struct DefaultBaseDir(pub args::DefaultBaseDir);

Expand Down Expand Up @@ -3371,6 +3395,8 @@ pub mod args {
DefaultFn(|| PortId::from_str("transfer").unwrap()),
);
pub const PRE_GENESIS: ArgFlag = flag("pre-genesis");
pub const PRIVATE_KEYS: ArgMulti<common::SecretKey, GlobStar> =
arg_multi("private-keys");
pub const PROPOSAL_ETH: ArgFlag = flag("eth");
pub const PROPOSAL_PGF_STEWARD: ArgFlag = flag("pgf-stewards");
pub const PROPOSAL_PGF_FUNDING: ArgFlag = flag("pgf-funding");
Expand Down Expand Up @@ -7849,6 +7875,40 @@ pub mod args {
}
}

#[derive(Clone, Debug)]
pub struct SignOffline {
pub tx_path: PathBuf,
pub secret_keys: Vec<common::SecretKey>,
pub owner: Address,
}

impl Args for SignOffline {
fn parse(matches: &ArgMatches) -> Self {
let tx_path = DATA_PATH.parse(matches);
let secret_keys = PRIVATE_KEYS.parse(matches);
let owner = RAW_ADDRESS.parse(matches);

Self {
tx_path,
secret_keys,
owner,
}
}

fn def(app: App) -> App {
app.arg(
DATA_PATH
.def()
.help(wrap!("The path to the serialized transaction.")),
)
.arg(PRIVATE_KEYS.def().help(wrap!(
"The set of private keys to use to sign the transaction. The \
order matters."
)))
.arg(RAW_ADDRESS.def().help(wrap!("The owner's address.")))
}
}

#[derive(Clone, Debug)]
pub struct DefaultBaseDir {}

Expand Down
3 changes: 3 additions & 0 deletions crates/apps_lib/src/cli/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,9 @@ impl CliApi {
ClientUtils::DefaultBaseDir(DefaultBaseDir(args)) => {
utils::default_base_dir(global_args, args)
}
ClientUtils::SignOffline(SignOffline(args)) => {
utils::sign_offline(global_args, args).await
}
ClientUtils::EpochSleep(EpochSleep(args)) => {
let mut ctx = cli::Context::new::<IO>(global_args)
.expect("expected to construct a context");
Expand Down
55 changes: 55 additions & 0 deletions crates/apps_lib/src/client/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use flate2::Compression;
use itertools::Either;
use namada_sdk::account::AccountPublicKeysMap;
use namada_sdk::args::DeviceTransport;
use namada_sdk::chain::ChainId;
use namada_sdk::dec::Dec;
use namada_sdk::key::*;
use namada_sdk::string_encoding::StringEncoded;
use namada_sdk::token;
use namada_sdk::tx::Tx;
use namada_sdk::uint::Uint;
use namada_sdk::wallet::{alias, Wallet};
use namada_vm::validate_untrusted_wasm;
Expand Down Expand Up @@ -998,6 +1000,59 @@ pub async fn sign_genesis_tx(
}
}

/// Offline sign a transactions.
pub async fn sign_offline(
_global_args: args::Global,
args::SignOffline {
tx_path,
secret_keys,
owner,
}: args::SignOffline,
) {
let tx_data = if let Ok(tx_data) = fs::read(&tx_path) {
tx_data
} else {
eprintln!("Couldn't open file at {}", tx_path.display());
safe_exit(1)
};

let tx = if let Ok(transaction) = Tx::deserialize(tx_data.as_ref()) {
transaction
} else {
eprintln!("Couldn't decode the transaction.");
safe_exit(1)
};

let account_public_keys_map = AccountPublicKeysMap::from_iter(
secret_keys.iter().map(|sk| sk.to_public()),
);

let signatures = tx.compute_section_signature(
&secret_keys,
&account_public_keys_map,
Some(owner),
);

for signature in &signatures {
let filename = format!(
"offline_signature_{}_{}.tx",
tx.header_hash().to_string().to_lowercase(),
signature.pubkey,
);

let signature_path = File::create(&filename)
.expect("Should be able to create signature file.");

serde_json::to_writer_pretty(signature_path, &signature)
.expect("Signature should be deserializable.");

println!(
"Signature for {} serialized at {}",
signature.pubkey, filename
);
}
}

/// Add a spinning wheel to a message for long running commands.
/// Can be turned off for E2E tests by setting the `REDUCED_CLI_PRINTING`
/// environment variable.
Expand Down
55 changes: 50 additions & 5 deletions crates/core/src/key/common.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
//! Cryptographic keys
use std::fmt::Display;
use std::fmt::{self, Display};
use std::str::FromStr;

use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use borsh_ext::BorshSerializeExt;
use data_encoding::HEXLOWER;
use data_encoding::{HEXLOWER, HEXUPPER};
use namada_macros::BorshDeserializer;
#[cfg(feature = "migrations")]
use namada_migrations::*;
#[cfg(any(test, feature = "rand"))]
use rand::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use serde::de::{self, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use thiserror::Error;

use super::{
Expand Down Expand Up @@ -307,8 +308,6 @@ impl FromStr for SecretKey {
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
BorshDeserializer,
Expand Down Expand Up @@ -341,6 +340,52 @@ impl string_encoding::Format for Signature {
}
}

impl Serialize for CommonSignature {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let hex_str = HEXUPPER.encode(&self.serialize_to_vec());
serializer.serialize_str(&hex_str)
}
}

// Implement custom deserialization
impl<'de> Deserialize<'de> for CommonSignature {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct SignatureVisitor;

impl<'de> Visitor<'de> for SignatureVisitor {
type Value = CommonSignature;

fn expecting(
&self,
formatter: &mut fmt::Formatter<'_>,
) -> fmt::Result {
formatter.write_str(
"a hex string representing either an Ed25519 or Secp256k1 \
signature",
)
}

fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
let bytes =
HEXUPPER.decode(value.as_bytes()).map_err(E::custom)?;
CommonSignature::try_from_slice(&bytes)
.map_err(|e| E::custom(e.to_string()))
}
}

deserializer.deserialize_str(SignatureVisitor)
}
}

impl_display_and_from_str_via_format!(Signature);

impl From<ed25519::Signature> for Signature {
Expand Down
2 changes: 1 addition & 1 deletion crates/core/src/key/secp256k1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ impl BorshSchema for SecretKey {

impl Display for SecretKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", HEXLOWER.encode(&self.0.to_bytes()))
write!(f, "{}", HEXLOWER.encode(self.0.to_bytes().as_ref()))
}
}

Expand Down
3 changes: 1 addition & 2 deletions crates/tx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ mod hex_data_serde {
use serde::{de, Deserializer, Serializer};

pub fn serialize<S>(
#[allow(clippy::ptr_arg)]
data: &Vec<u8>,
#[allow(clippy::ptr_arg)] data: &Vec<u8>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
Expand Down

0 comments on commit 6c3acac

Please sign in to comment.