Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cosmrs: cosmos.crypto.multisig.LegacyAminoPubKey support #147

Merged
merged 3 commits into from
Oct 28, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
tony-iqlusion marked this conversation as resolved.
Show resolved Hide resolved
);
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
)
})
tony-iqlusion marked this conversation as resolved.
Show resolved Hide resolved
}
}

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()
)
}),
}
}
}