Skip to content

Commit

Permalink
chore: add sdk::PublicKey to workspaces::PublicKey conversion (#267)
Browse files Browse the repository at this point in the history
  • Loading branch information
ChaoticTempest authored Jul 30, 2023
1 parent 13f3a84 commit 4739705
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build files
/target
**/target
Cargo.lock

#include all target folders
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@

- [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`
- NOTE: Borsh bytes format is the same as near-sdk, where it is in the form of [bytes_len, key_type, key_data..]
- 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`.
- [Added `TryFrom<near_sdk::PublicKey>` for `workspaces::PublicKey`](https://github.com/near/workspaces-rs/pull/267)
- Added `KeyType::len` and `PublicKey::try_from_bytes`

### Changed

Expand Down
4 changes: 3 additions & 1 deletion workspaces/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ tokio-retry = "0.3"
tracing = "0.1"
url = { version = "2.2.2", features = ["serde"] }

near-sdk = { version = "4.1", optional = true }
near-account-id = "0.16"
near-crypto = "0.16"
near-primitives = "0.16"
Expand All @@ -55,8 +56,9 @@ test-log = { version = "0.2.8", default-features = false, features = ["trace"] }
tracing-subscriber = { version = "0.3.5", features = ["env-filter"] }

[features]
default = ["install"]
default = ["install", "interop_sdk"]
install = [] # Install the sandbox binary during compile time
interop_sdk = ["near-sdk"]
unstable = ["cargo_metadata"]

[package.metadata.docs.rs]
Expand Down
81 changes: 68 additions & 13 deletions workspaces/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ pub(crate) mod account;
pub(crate) mod block;
pub(crate) mod chunk;

#[cfg(feature = "interop_sdk")]
mod sdk;

use std::convert::TryFrom;
use std::fmt::{self, Debug, Display};
use std::io;
use std::path::Path;
use std::str::FromStr;

Expand Down Expand Up @@ -63,6 +67,14 @@ impl KeyType {
near_crypto::KeyType::SECP256K1 => Self::SECP256K1,
}
}

/// Length of the bytes of the public key associated with this key type.
pub const fn data_len(&self) -> usize {
match self {
Self::ED25519 => 32,
Self::SECP256K1 => 64,
}
}
}

impl Display for KeyType {
Expand Down Expand Up @@ -103,19 +115,7 @@ 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,
Hash,
Eq,
PartialEq,
Ord,
PartialOrd,
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
)]
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
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.
Expand All @@ -126,6 +126,31 @@ impl PublicKey {
Self(near_crypto::PublicKey::empty(key_type.into_near_keytype()))
}

/// Create a new [`PublicKey`] from the given bytes. This will return an error if the bytes are not in the
/// correct format. Expected to have key type be the first byte encoded, with the remaining bytes being the
/// key data.
pub fn try_from_parts(key_type: KeyType, bytes: &[u8]) -> Result<Self> {
let mut buf = Vec::new();
buf.push(key_type as u8);
buf.extend(bytes);
Ok(Self(near_crypto::PublicKey::try_from_slice(&buf).map_err(
|e| {
ErrorKind::DataConversion
.full(format!("Invalid key data for key type: {key_type}"), e)
},
)?))
}

fn try_from_bytes(bytes: &[u8]) -> Result<Self> {
let key_type = KeyType::try_from(bytes[0])?;
Ok(Self(
near_crypto::PublicKey::try_from_slice(bytes).map_err(|e| {
ErrorKind::DataConversion
.full(format!("Invalid key data for key type: {key_type}"), e)
})?,
))
}

/// 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.
Expand Down Expand Up @@ -162,6 +187,36 @@ impl FromStr for PublicKey {
}
}

impl BorshSerialize for PublicKey {
fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
// NOTE: sdk::PublicKey requires that we serialize the length of the key first, then the key itself.
// Casted usize to u32 since the length in WASM is only 4 bytes long.
BorshSerialize::serialize(&(self.len() as u32), writer)?;
// Serialize key type and key data:
BorshSerialize::serialize(&self.0, writer)
}
}

impl BorshDeserialize for PublicKey {
fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
let len: u32 = BorshDeserialize::deserialize_reader(reader)?;
let pk: near_crypto::PublicKey = BorshDeserialize::deserialize_reader(reader)?;

// Check that the length of the key matches the length we read from the buffer:
if pk.len() != len as usize {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Key length of {} does not match length of {} read from buffer",
pk.len(),
len
),
));
}
Ok(Self(pk))
}
}

/// Secret key of an account on chain. Usually created along with a [`PublicKey`]
/// to form a keypair associated to the account. To generate a new keypair, use
/// one of the creation methods found here, such as [`SecretKey::from_seed`]
Expand Down
18 changes: 18 additions & 0 deletions workspaces/src/types/sdk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use std::convert::TryFrom;

use crate::error::{Error, ErrorKind};

use super::PublicKey;

impl TryFrom<near_sdk::PublicKey> for PublicKey {
type Error = Error;

fn try_from(pk: near_sdk::PublicKey) -> Result<Self, Self::Error> {
Self::try_from_bytes(pk.as_bytes()).map_err(|e| {
ErrorKind::DataConversion.full(
"Could not convert sdk::PublicKey into workspaces::PublicKey",
e,
)
})
}
}
24 changes: 24 additions & 0 deletions workspaces/tests/test-contracts/type-serialize/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "test-contract-type-serialization"
version = "0.0.0"
authors = ["Near Inc <[email protected]>"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
anyhow = "1.0"
bs58 = "0.4"
near-sdk = "4.1"

[profile.release]
codegen-units = 1
# Tell `rustc` to optimize for small code size.
opt-level = "z"
lto = true
debug = false
panic = "abort"
overflow-checks = true

[workspace]
4 changes: 4 additions & 0 deletions workspaces/tests/test-contracts/type-serialize/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh

cargo build --target wasm32-unknown-unknown --release
cp target/wasm32-unknown-unknown/release/test_contract_type_serialization.wasm ./res/
Binary file not shown.
41 changes: 41 additions & 0 deletions workspaces/tests/test-contracts/type-serialize/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::near_bindgen;
use near_sdk::{CurveType, PublicKey};

use std::convert::TryFrom;

#[derive(Default, BorshSerialize, BorshDeserialize)]
#[near_bindgen]
struct Contract {}

#[near_bindgen]
impl Contract {
pub fn pass_pk_back_and_forth(&self, pk: PublicKey) -> PublicKey {
let mut data = vec![CurveType::ED25519 as u8];
data.extend(
bs58::decode("6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp")
.into_vec()
.expect("could not convert bs58 to vec"),
);
let pk_expected =
PublicKey::try_from(data).expect("could not create public key from parts");

assert_eq!(pk, pk_expected);
pk
}

#[result_serializer(borsh)]
pub fn pass_borsh_pk_back_and_forth(&self, #[serializer(borsh)] pk: PublicKey) -> PublicKey {
let mut data = vec![CurveType::ED25519 as u8];
data.extend(
bs58::decode("6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp")
.into_vec()
.expect("could not convert bs58 to vec"),
);
let pk_expected =
PublicKey::try_from(data).expect("could not create public key from parts");

assert_eq!(pk, pk_expected);
pk
}
}
42 changes: 37 additions & 5 deletions workspaces/tests/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ use borsh::{BorshDeserialize, BorshSerialize};
use workspaces::types::{KeyType, PublicKey, SecretKey};
use workspaces::AccountId;

fn default_workspaces_pubkey() -> anyhow::Result<PublicKey> {
let data = bs58::decode("279Zpep9MBBg4nKsVmTQE7NbXZkWdxti6HS1yzhp8qnc1ExS7gU").into_vec()?;
Ok(PublicKey::try_from_slice(data.as_slice())?)
}

#[test]
fn test_keypair_ed25519() -> anyhow::Result<()> {
let pk_expected = "\"ed25519:DcA2MzgpJbrUATQLLceocVckhhAqrkingax4oJ9kZ847\"";
Expand Down Expand Up @@ -52,15 +57,42 @@ fn test_pubkey_serialization() -> anyhow::Result<()> {
Ok(())
}

#[cfg(feature = "interop_sdk")]
#[tokio::test]
async fn test_pubkey_from_sdk_ser() -> anyhow::Result<()> {
const TYPE_SER_BYTES: &[u8] =
include_bytes!("test-contracts/type-serialize/res/test_contract_type_serialization.wasm");
let worker = workspaces::sandbox().await?;
let contract = worker.dev_deploy(TYPE_SER_BYTES).await?;

// Test out serde serialization and deserialization for PublicKey
let ws_pk = default_workspaces_pubkey()?;
let sdk_pk: PublicKey = contract
.call("pass_pk_back_and_forth")
.args_json(serde_json::json!({ "pk": ws_pk }))
.transact()
.await?
.json()?;
assert_eq!(ws_pk, sdk_pk);

// Test out borsh serialization and deserialization for PublicKey
let sdk_pk: PublicKey = contract
.call("pass_borsh_pk_back_and_forth")
.args_borsh(&ws_pk)
.transact()
.await?
.borsh()?;
assert_eq!(ws_pk, sdk_pk);

Ok(())
}

#[test]
fn test_pubkey_borsh_format_change() -> anyhow::Result<()> {
let mut data = vec![KeyType::ED25519 as u8];
data.extend(bs58::decode("6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp").into_vec()?);

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

Ok(())
Expand Down

0 comments on commit 4739705

Please sign in to comment.