Skip to content

Commit

Permalink
cosmrs: cosmos.crypto.multisig.LegacyAminoPubKey support
Browse files Browse the repository at this point in the history
Adds support for this key type when used as the `public_key` field of
`SignerInfo`.

To accomodate this, a new `SignerPublicKey` enum was added with variants
for a single `PublicKey` or a `LegacyAminoMultisig` key.

Decodes a test vector from `cosmoshub-4` (sdk@v0.42.10)

Closes #143
  • Loading branch information
tony-iqlusion committed Oct 28, 2021
1 parent 9484409 commit 069790f
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 5 deletions.
3 changes: 3 additions & 0 deletions cosmrs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ tokio = { version = "1", optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2", features = ["js"] }

[dev-dependencies]
hex-literal = "0.3"

[features]
default = ["bip32"]
dev = ["rpc", "tokio"]
Expand Down
5 changes: 4 additions & 1 deletion cosmrs/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
pub mod secp256k1;

mod compact_bit_array;
mod legacy_amino;
mod public_key;

pub use self::{compact_bit_array::CompactBitArray, public_key::PublicKey};
pub use self::{
compact_bit_array::CompactBitArray, legacy_amino::LegacyAminoMultisig, public_key::PublicKey,
};
103 changes: 103 additions & 0 deletions cosmrs/src/crypto/legacy_amino.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//! Legacy Amino support.
use super::PublicKey;
use crate::{prost_ext::MessageExt, proto, Any, Error, ErrorReport, Result};
use eyre::WrapErr;
use prost::Message;

/// Legacy Amino multisig key.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LegacyAminoMultisig {
/// Multisig threshold.
pub threshold: u32,

/// Public keys which comprise the multisig key.
pub public_keys: Vec<PublicKey>,
}

impl LegacyAminoMultisig {
/// Protobuf [`Any`] type URL for [`LegacyAminoMultisig`].
pub const TYPE_URL: &'static str = "/cosmos.crypto.multisig.LegacyAminoPubKey";
}

impl From<LegacyAminoMultisig> for Any {
fn from(amino_multisig: LegacyAminoMultisig) -> Any {
let proto = proto::cosmos::crypto::multisig::LegacyAminoPubKey {
threshold: amino_multisig.threshold,
public_keys: amino_multisig
.public_keys
.into_iter()
.map(|pk| pk.into())
.collect(),
};

Any {
type_url: LegacyAminoMultisig::TYPE_URL.to_owned(),
value: proto
.to_bytes()
.expect("LegacyAminoPubKey serialization error"),
}
}
}

impl TryFrom<Any> for LegacyAminoMultisig {
type Error = ErrorReport;

fn try_from(any: Any) -> Result<LegacyAminoMultisig> {
LegacyAminoMultisig::try_from(&any)
}
}

impl TryFrom<&Any> for LegacyAminoMultisig {
type Error = ErrorReport;

fn try_from(any: &Any) -> Result<Self> {
if any.type_url != Self::TYPE_URL {
return Err(Error::Crypto).wrap_err_with(|| {
format!("invalid type URL for LegacyAminoPubKey: {}", &any.type_url)
});
}

let proto = proto::cosmos::crypto::multisig::LegacyAminoPubKey::decode(&*any.value)?;
let public_keys = proto
.public_keys
.into_iter()
.map(PublicKey::try_from)
.collect::<Result<Vec<_>, _>>()?;

Ok(Self {
threshold: proto.threshold,
public_keys,
})
}
}

#[cfg(test)]
mod tests {
use super::LegacyAminoMultisig;
use crate::Any;
use hex_literal::hex;

#[test]
fn any_round_trip() {
let any = Any {
type_url: "/cosmos.crypto.multisig.LegacyAminoPubKey".to_owned(),
value: hex!("080312460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210316eb99be27392e258ded83dc1378e507acf1bb726fa407167e709461b3a631cb12460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210363deebf13d30a9840f275d01911f3e05f3fb5f88554f52b2ef534dce06b1da5912460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21032e253cf8214f3d466ed296b9919821ae6681806c91b3c2063a45a8b85ce7e11512460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210326ffd12bd115f260a371f2f09bf29286e4c9681c7bc109f4604c82ed82d6d23212460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210343a3b485021493370286c9f4725358a3fd459576f963dcc158cb82c02276b67f").into(),
};

let pk = LegacyAminoMultisig::try_from(&any).unwrap();
assert_eq!(pk.threshold, 3);
assert_eq!(pk.public_keys.len(), 5);
assert_eq!(
pk.public_keys[0].type_url(),
"/cosmos.crypto.secp256k1.PubKey"
);
assert_eq!(
pk.public_keys[0].to_bytes().as_slice(),
&hex!("0316eb99be27392e258ded83dc1378e507acf1bb726fa407167e709461b3a631cb")
);

// Ensure serialized key round trips
assert_eq!(any, Any::from(pk));
}
}
2 changes: 1 addition & 1 deletion cosmrs/src/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ pub use self::{
msg::{Msg, MsgProto},
raw::Raw,
sign_doc::SignDoc,
signer_info::SignerInfo,
signer_info::{SignerInfo, SignerPublicKey},
};
pub use crate::{proto::cosmos::tx::signing::v1beta1::SignMode, ErrorReport};
pub use tendermint::abci::{transaction::Hash, Gas};
Expand Down
112 changes: 109 additions & 3 deletions cosmrs/src/tx/signer_info.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
//! Signer info.
use super::{AuthInfo, Fee, ModeInfo, SequenceNumber, SignMode};
use crate::{crypto::PublicKey, proto, Error, ErrorReport, Result};
use crate::{
crypto::{LegacyAminoMultisig, PublicKey},
proto, Any, Error, ErrorReport, Result,
};
use eyre::WrapErr;

/// [`SignerInfo`] describes the public key and signing mode of a single top-level signer.
#[derive(Clone, Debug, Eq, PartialEq)]
Expand All @@ -10,7 +14,7 @@ pub struct SignerInfo {
///
/// It is optional for accounts that already exist in state. If unset, the verifier can use the
/// required signer address for this position and lookup the public key.
pub public_key: Option<PublicKey>,
pub public_key: Option<SignerPublicKey>,

/// Signing mode.
///
Expand All @@ -28,7 +32,7 @@ impl SignerInfo {
/// Create [`SignerInfo`] for a single direct signer.
pub fn single_direct(public_key: Option<PublicKey>, sequence: SequenceNumber) -> SignerInfo {
SignerInfo {
public_key,
public_key: public_key.map(Into::into),
mode_info: ModeInfo::single(SignMode::Direct),
sequence,
}
Expand Down Expand Up @@ -69,3 +73,105 @@ impl From<SignerInfo> for proto::cosmos::tx::v1beta1::SignerInfo {
}
}
}

/// Signer's public key.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SignerPublicKey {
/// Single singer.
Single(PublicKey),

/// Legacy Amino multisig.
LegacyAminoMultisig(LegacyAminoMultisig),
}

impl SignerPublicKey {
/// Get the type URL for this [`SignerPublicKey`].
pub fn type_url(&self) -> &'static str {
match self {
Self::Single(pk) => pk.type_url(),
Self::LegacyAminoMultisig(_) => LegacyAminoMultisig::TYPE_URL,
}
}

/// Get the [`PublicKey`] for a single signer, if applicable.
pub fn single(&self) -> Option<&PublicKey> {
match self {
Self::Single(pk) => Some(pk),
_ => None,
}
}

/// Get the [`LegacyAminoMultisig`] key info, if applicable.
pub fn legacy_amino_multisig(&self) -> Option<&LegacyAminoMultisig> {
match self {
Self::LegacyAminoMultisig(amino_multisig) => Some(amino_multisig),
_ => None,
}
}
}

impl From<PublicKey> for SignerPublicKey {
fn from(pk: PublicKey) -> SignerPublicKey {
Self::Single(pk)
}
}

impl From<LegacyAminoMultisig> for SignerPublicKey {
fn from(pk: LegacyAminoMultisig) -> SignerPublicKey {
Self::LegacyAminoMultisig(pk)
}
}

impl From<SignerPublicKey> for Any {
fn from(public_key: SignerPublicKey) -> Any {
match public_key {
SignerPublicKey::Single(pk) => pk.into(),
SignerPublicKey::LegacyAminoMultisig(pk) => pk.into(),
}
}
}

impl TryFrom<Any> for SignerPublicKey {
type Error = ErrorReport;

fn try_from(any: Any) -> Result<SignerPublicKey> {
SignerPublicKey::try_from(&any)
}
}

impl TryFrom<&Any> for SignerPublicKey {
type Error = ErrorReport;

fn try_from(any: &Any) -> Result<Self> {
if let Ok(pk) = PublicKey::try_from(any) {
return Ok(pk.into());
}

if let Ok(pk) = LegacyAminoMultisig::try_from(any) {
return Ok(pk.into());
}

Err(Error::Crypto).wrap_err_with(|| {
format!(
"invalid type URL for SignerInfo public key: {}",
&any.type_url
)
})
}
}

impl TryFrom<SignerPublicKey> for PublicKey {
type Error = ErrorReport;

fn try_from(public_key: SignerPublicKey) -> Result<PublicKey> {
match public_key {
SignerPublicKey::Single(pk) => Ok(pk),
_ => Err(Error::Crypto).wrap_err_with(|| {
format!(
"expected `SignerPublicKey::Single`, got: {}",
public_key.type_url()
)
}),
}
}
}

0 comments on commit 069790f

Please sign in to comment.