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

chore: Import some functions over from near_crypto for PublicKey #265

Merged
merged 9 commits into from
Jan 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

### Added

- [Import a couple functions over from near_crypto for PublicKey](https://github.com/near/workspaces-rs/pull/265)
- Impl `Ord`, `PartialOrd`, `Hash`, `BorshSerialize`, `BorshDeserialize`, `Display`, and `FromStr` for `PublicKey`
- Added `PublicKey::{empty, len, key_data}`
- Impl `Display` for `SecretKey`.
- more docs were added to both `SecretKey` and `PublicKey`.
- Impl `Display`, `FromStr`, `TryFrom<u8>` for `KeyType`.

### Changed

- [`Transaction::transact_async` no longer has a lifetime parameter to make it easier to use](https://github.com/near/workspaces-rs/pull/249)
Expand Down
118 changes: 106 additions & 12 deletions workspaces/src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
//! Types used in the workspaces crate. A lot of these are types are copied over from near_primitives
//! since those APIs are not yet stable. Once they are, we can directly reference them here, so no
//! changes on the library consumer side is needed. Just keep using these types defined here as-is.

pub(crate) mod account;
pub(crate) mod block;
pub(crate) mod chunk;

/// Types copied over from near_primitives since those APIs are not yet stable.
/// and internal libraries like near-jsonrpc-client requires specific versions
/// of these types which shouldn't be exposed either.
use std::convert::TryFrom;
use std::fmt;
use std::fmt::{self, Debug, Display};
use std::path::Path;
use std::str::FromStr;

use borsh::{BorshDeserialize, BorshSerialize};
pub use near_account_id::AccountId;
use near_primitives::logging::pretty_hash;
use near_primitives::serialize::to_base58;
Expand Down Expand Up @@ -44,8 +47,8 @@ fn from_base58(s: &str) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sy
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub enum KeyType {
ED25519,
SECP256K1,
ED25519 = 0,
SECP256K1 = 1,
}

impl KeyType {
Expand All @@ -64,6 +67,36 @@ impl KeyType {
}
}

impl Display for KeyType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.into_near_keytype())
}
}

impl FromStr for KeyType {
type Err = Error;

fn from_str(value: &str) -> Result<Self, Self::Err> {
let key_type = near_crypto::KeyType::from_str(value)
.map_err(|e| ErrorKind::DataConversion.custom(e))?;

Ok(Self::from_near_keytype(key_type))
}
}

impl TryFrom<u8> for KeyType {
type Error = Error;

fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(KeyType::ED25519),
1 => Ok(KeyType::SECP256K1),
unknown_key_type => Err(ErrorKind::DataConversion
.custom(format!("Unknown key type provided: {unknown_key_type}"))),
}
}
}

impl From<PublicKey> for near_crypto::PublicKey {
fn from(pk: PublicKey) -> Self {
pk.0
Expand All @@ -72,13 +105,63 @@ impl From<PublicKey> for near_crypto::PublicKey {

/// Public key of an account on chain. Usually created along with a [`SecretKey`]
/// to form a keypair associated to the account.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[derive(
Debug,
Clone,
Hash,
Eq,
PartialEq,
Ord,
PartialOrd,
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
)]
pub struct PublicKey(pub(crate) near_crypto::PublicKey);

#[allow(clippy::len_without_is_empty)] // PublicKey is guaranteed to never be empty due to KeyType restrictions.
impl PublicKey {
/// Create an empty `PublicKey` with the given [`KeyType`]. This is a zero-ed out public key with the
/// length of the bytes determined by the associated key type.
pub fn empty(key_type: KeyType) -> Self {
Self(near_crypto::PublicKey::empty(key_type.into_near_keytype()))
}

/// Get the number of bytes this key uses. This will differ depending on the [`KeyType`]. i.e. for
/// ED25519 keys, this will return 32 + 1, while for SECP256K1 keys, this will return 64 + 1. The +1
/// is used to store the key type, and will appear at the start of the serialized key.
pub fn len(&self) -> usize {
self.0.len()
}

/// Get the [`KeyType`] of the public key.
pub fn key_type(&self) -> KeyType {
KeyType::from_near_keytype(self.0.key_type())
}

/// Get the key data of the public key. This is serialized bytes of the public key. This will not
/// include the key type, and will only contain the raw key data.
pub fn key_data(&self) -> &[u8] {
self.0.key_data()
}
}

impl Display for PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}

impl FromStr for PublicKey {
type Err = Error;

fn from_str(value: &str) -> Result<Self, Self::Err> {
let pk = near_crypto::PublicKey::from_str(value)
.map_err(|e| ErrorKind::DataConversion.custom(e))?;

Ok(Self(pk))
}
}

/// Secret key of an account on chain. Usually created along with a [`PublicKey`]
Expand All @@ -88,26 +171,37 @@ impl PublicKey {
pub struct SecretKey(pub(crate) near_crypto::SecretKey);

impl SecretKey {
/// Get the [`KeyType`] of the secret key.
pub fn key_type(&self) -> KeyType {
KeyType::from_near_keytype(self.0.key_type())
}

/// Get the [`PublicKey`] associated to this secret key.
pub fn public_key(&self) -> PublicKey {
PublicKey(self.0.public_key())
}

/// Generate a new secret key provided the [`KeyType`] and seed.
pub fn from_seed(key_type: KeyType, seed: &str) -> Self {
let key_type = key_type.into_near_keytype();
Self(near_crypto::SecretKey::from_seed(key_type, seed))
}

/// Generate a new secret key provided the [`KeyType`]. This will use OS provided entropy
/// to generate the key.
pub fn from_random(key_type: KeyType) -> Self {
let key_type = key_type.into_near_keytype();
Self(near_crypto::SecretKey::from_random(key_type))
}
}

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

impl FromStr for SecretKey {
type Err = Error;

fn from_str(value: &str) -> Result<Self, Self::Err> {
Expand Down Expand Up @@ -154,7 +248,7 @@ impl InMemorySigner {
#[derive(Copy, Clone, Default, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct CryptoHash(pub [u8; 32]);

impl std::str::FromStr for CryptoHash {
impl FromStr for CryptoHash {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Expand Down Expand Up @@ -190,15 +284,15 @@ impl TryFrom<Vec<u8>> for CryptoHash {
}
}

impl fmt::Debug for CryptoHash {
impl Debug for CryptoHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", pretty_hash(&self.to_string()))
}
}

impl fmt::Display for CryptoHash {
impl Display for CryptoHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&to_base58(self.0), f)
Display::fmt(&to_base58(self.0), f)
}
}

Expand Down
35 changes: 34 additions & 1 deletion workspaces/tests/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::str::FromStr;

use workspaces::types::{KeyType, SecretKey};
use borsh::{BorshDeserialize, BorshSerialize};

use workspaces::types::{KeyType, PublicKey, SecretKey};
use workspaces::AccountId;

#[test]
Expand Down Expand Up @@ -33,6 +35,37 @@ fn test_keypair_secp256k1() -> anyhow::Result<()> {
Ok(())
}

#[test]
fn test_pubkey_serialization() -> anyhow::Result<()> {
for key_type in [KeyType::ED25519, KeyType::SECP256K1] {
let sk = SecretKey::from_seed(key_type, "test");
let pk = sk.public_key();
let bytes = pk.try_to_vec()?;

// Borsh Deserialization should equate to the original public key:
assert_eq!(PublicKey::try_from_slice(&bytes)?, pk);

// invalid public key should error out on deserialization:
assert!(PublicKey::try_from_slice(&[0]).is_err());
}

Ok(())
}

#[test]
fn test_pubkey_borsh_format_change() -> anyhow::Result<()> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. Struggling a bit to understand this test.

Is there a case where this would fail that's directly impacted by the struct here? Otherwise, it looks like a data test is all we're trying to do.

So this would be equivalent, no?

let mut data = vec![KeyType::ED25519 as u8];
data.extend(bs58::decode("6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp").into_vec()?);

let new_key = PublicKey::try_from_slice(data.as_slice())?;

assert_eq!(
    new_key.try_to_vec()?,
    bs58::decode("16E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp").into_vec()?
);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, now that you mention it, yeah it should be equivalent. I wonder why SDK did it like this in the first place

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh it's because it had Vec<u8>.try_into() but I just got rid of it in favor of borsh

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, gotcha!

let mut data = vec![KeyType::ED25519 as u8];
data.extend(bs58::decode("6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp").into_vec()?);

let pk = PublicKey::try_from_slice(data.as_slice())?;
assert_eq!(
pk.try_to_vec()?,
bs58::decode("16E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp").into_vec()?
);

Ok(())
}

#[test]
fn test_valid_account_id() {
let account_id = "testnet";
Expand Down