diff --git a/Cargo.lock b/Cargo.lock index 4cfb518e..2373df3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,6 +69,12 @@ dependencies = [ "libc", ] +[[package]] +name = "base64" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5ca2cd0adc3f48f9e9ea5a6bbdf9ccc0bfade884847e484d452414c7ccffb3" + [[package]] name = "block-buffer" version = "0.7.3" @@ -785,6 +791,7 @@ name = "stdtx" version = "0.1.0" dependencies = [ "anomaly", + "base64", "ecdsa", "prost-amino", "prost-amino-derive", diff --git a/stdtx/Cargo.toml b/stdtx/Cargo.toml index 4c4e0e23..5e463e15 100644 --- a/stdtx/Cargo.toml +++ b/stdtx/Cargo.toml @@ -16,6 +16,7 @@ circle-ci = { repository = "tendermint/kms" } [dependencies] anomaly = { version = "0.2", path = "../anomaly" } +base64 = "0.12.0" ecdsa = { version = "0.4", features = ["k256"] } prost-amino = "0.5" prost-amino-derive = "0.5" diff --git a/stdtx/src/address.rs b/stdtx/src/address.rs index 51f0a46c..09d8e49a 100644 --- a/stdtx/src/address.rs +++ b/stdtx/src/address.rs @@ -2,6 +2,7 @@ use crate::error::{Error, ErrorKind}; use anomaly::ensure; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use std::convert::TryInto; use subtle_encoding::bech32; @@ -34,6 +35,25 @@ impl Address { } } +impl Serialize for Address { + fn serialize(&self, serializer: S) -> Result { + let s = self.to_bech32("cosmos"); // TODO: how to generalize? + s.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Address { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + let (_hrp, addr) = + Address::from_bech32(s).map_err(|e| de::Error::custom(format!("{:?}", e)))?; + Ok(addr) + } +} + impl AsRef<[u8]> for Address { fn as_ref(&self) -> &[u8] { &self.0 diff --git a/stdtx/src/amino_types.rs b/stdtx/src/amino_types.rs index e1681d46..7f272749 100644 --- a/stdtx/src/amino_types.rs +++ b/stdtx/src/amino_types.rs @@ -3,6 +3,9 @@ use crate::{Signature, TypeName}; use prost_amino::{encode_length_delimiter, Message}; use prost_amino_derive::Message; +use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; + +use base64; use serde_json::json; /// StdTx Amino type @@ -40,7 +43,7 @@ impl StdTx { } /// StdFee amino type -#[derive(Clone, Message)] +#[derive(Clone, Message, Serialize, Deserialize)] pub struct StdFee { /// Fee to be paid #[prost_amino(message, repeated, tag = "1")] @@ -48,9 +51,28 @@ pub struct StdFee { /// Gas requested for transaction #[prost_amino(uint64)] + #[serde(serialize_with = "serialize_u64", deserialize_with = "parse_u64")] pub gas: u64, } +/// Serialize u64 as a string for proto3 JSON encoding. +pub(crate) fn serialize_u64(value: &u64, serializer: S) -> Result +where + S: Serializer, +{ + format!("{}", value).serialize(serializer) +} + +/// Parse u64 from a string for proto3 JSON decoding. +pub(crate) fn parse_u64<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + String::deserialize(deserializer)? + .parse::() + .map_err(|e| D::Error::custom(format!("{}", e))) +} + impl StdFee { /// Create a [`StdFee`] for a gas-only transaction pub fn for_gas(gas: u64) -> Self { @@ -75,7 +97,7 @@ impl StdFee { } /// Coin Amino type -#[derive(Clone, Message)] +#[derive(Clone, Message, Serialize, Deserialize)] pub struct Coin { /// Denomination of coin #[prost_amino(string, tag = "1")] @@ -97,21 +119,41 @@ impl Coin { } /// StdSignature amino type -#[derive(Clone, Message)] +#[derive(Clone, Message, Serialize, Deserialize)] pub struct StdSignature { /// Public key which can verify this signature #[prost_amino(bytes, tag = "1", amino_name = "tendermint/PubKeySecp256k1")] - pub pub_key: Vec, + #[serde(serialize_with = "serialize_base64", deserialize_with = "parse_base64")] + pub public_key: Vec, /// Serialized signature #[prost_amino(bytes)] + #[serde(serialize_with = "serialize_base64", deserialize_with = "parse_base64")] pub signature: Vec, } +/// Serialize bytes as base64 string for proto3 JSON encoding. +pub(crate) fn serialize_base64(value: &[u8], serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_str(&base64::encode(value)) +} + +/// Parse bytes from a base64 string for proto3 JSON decoding. +pub(crate) fn parse_base64<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + String::deserialize(deserializer).and_then(|string| { + base64::decode(&string).map_err(|err| D::Error::custom(format!("{}", err))) + }) +} + impl From for StdSignature { fn from(signature: Signature) -> StdSignature { StdSignature { - pub_key: vec![], + public_key: vec![], signature: signature.as_ref().to_vec(), } }