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: add sdk::PublicKey to workspaces::PublicKey conversion #267

Merged
merged 8 commits into from
Jul 30, 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
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 {
ChaoticTempest marked this conversation as resolved.
Show resolved Hide resolved
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 <hello@nearprotocol.com>"]
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