Skip to content

Commit

Permalink
ETH-implicit accounts support
Browse files Browse the repository at this point in the history
  • Loading branch information
staffik committed Nov 8, 2023
1 parent a78dbe0 commit f61f68a
Show file tree
Hide file tree
Showing 22 changed files with 635 additions and 175 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion chain/jsonrpc/res/rpc_errors_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,8 @@
"MethodNameMismatch",
"RequiresFullAccess",
"NotEnoughAllowance",
"DepositWithFunctionCall"
"DepositWithFunctionCall",
"InvalidPkForEthAddress"
],
"props": {}
},
Expand Down Expand Up @@ -487,6 +488,14 @@
"tx_nonce": ""
}
},
"InvalidPkForEthAddress": {
"name": "InvalidPkForEthAddress",
"subtypes": [],
"props": {
"account_id": "",
"public_key": ""
}
},
"InvalidPredecessorId": {
"name": "InvalidPredecessorId",
"subtypes": [],
Expand Down
7 changes: 7 additions & 0 deletions core/crypto/src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,13 @@ impl PublicKey {
Self::SECP256K1(_) => panic!(),
}
}

pub fn unwrap_as_secp256k1(&self) -> &Secp256K1PublicKey {
match self {
Self::SECP256K1(key) => key,
Self::ED25519(_) => panic!(),
}
}
}

// This `Hash` implementation is safe since it retains the property
Expand Down
5 changes: 4 additions & 1 deletion core/crypto/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ impl PublicKey {
let keypair = ed25519_key_pair_from_seed(seed);
PublicKey::ED25519(ED25519PublicKey(keypair.public.to_bytes()))
}
_ => unimplemented!(),
KeyType::SECP256K1 => {
let secret_key = SecretKey::SECP256K1(secp256k1_secret_key_from_seed(seed));
PublicKey::SECP256K1(secret_key.public_key().unwrap_as_secp256k1().clone())
}
}
}
}
Expand Down
12 changes: 8 additions & 4 deletions core/primitives-core/src/runtime/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,10 @@ pub fn transfer_exec_fee(
(_, AccountType::NamedAccount) => transfer_fee,
// No account will be created, just a regular transfer.
(false, _) => transfer_fee,
// Currently, no account is created on transfer to ETH-implicit account, just a regular transfer.
(true, AccountType::EthImplicitAccount) => transfer_fee,
// Extra fee for the CreateAccount.
(true, AccountType::EthImplicitAccount) => {
transfer_fee + cfg.fee(ActionCosts::create_account).exec_fee()
}
// Extra fees for the CreateAccount and AddFullAccessKey.
(true, AccountType::NearImplicitAccount) => {
transfer_fee
Expand All @@ -240,8 +242,10 @@ pub fn transfer_send_fee(
(_, AccountType::NamedAccount) => transfer_fee,
// No account will be created, just a regular transfer.
(false, _) => transfer_fee,
// Currently, no account is created on transfer to ETH-implicit account, just a regular transfer.
(true, AccountType::EthImplicitAccount) => transfer_fee,
// Extra fee for the CreateAccount.
(true, AccountType::EthImplicitAccount) => {
transfer_fee + cfg.fee(ActionCosts::create_account).send_fee(sender_is_receiver)
}
// Extra fees for the CreateAccount and AddFullAccessKey.
(true, AccountType::NearImplicitAccount) => {
transfer_fee
Expand Down
1 change: 1 addition & 0 deletions core/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ serde.workspace = true
serde_json.workspace = true
serde_with.workspace = true
serde_yaml.workspace = true
sha3.workspace = true
smart-default.workspace = true
stdx.workspace = true
strum.workspace = true
Expand Down
11 changes: 9 additions & 2 deletions core/primitives/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ pub enum InvalidAccessKeyError {
},
/// Having a deposit with a function call action is not allowed with a function call access key.
DepositWithFunctionCall,
/// ETH-implicit `account_id` isn't derived from the `public_key`.
InvalidPkForEthAddress { account_id: AccountId, public_key: PublicKey },
}

/// Describes the error for validating a list of actions.
Expand Down Expand Up @@ -487,8 +489,8 @@ pub enum ActionErrorKind {
/// receipt validation.
NewReceiptValidationError(ReceiptValidationError),
/// Error occurs when a `CreateAccount` action is called on hex-characters
/// account of length 64. See implicit account creation NEP:
/// <https://github.com/nearprotocol/NEPs/pull/71>.
/// account of length 64 or 42 (when starting with '0x').
/// See implicit account creation NEP: <https://github.com/nearprotocol/NEPs/pull/71>.
///
/// TODO(#8598): This error is named very poorly. A better name would be
/// `OnlyNamedAccountCreationAllowed`.
Expand Down Expand Up @@ -612,6 +614,11 @@ impl Display for InvalidAccessKeyError {
InvalidAccessKeyError::DepositWithFunctionCall => {
write!(f, "Having a deposit with a function call action is not allowed with a function call access key.")
}
InvalidAccessKeyError::InvalidPkForEthAddress { account_id, public_key } => write!(
f,
"ETH-implicit address {:?} isn't derived from the public_key {}",
account_id, public_key
),
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions core/primitives/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,8 @@ pub fn create_user_test_signer(account_name: &AccountIdRef) -> InMemorySigner {
let account_id = account_name.to_owned();
if account_id == near_implicit_test_account() {
InMemorySigner::from_secret_key(account_id, near_implicit_test_account_secret())
} else if account_id == eth_implicit_test_account() {
InMemorySigner::from_secret_key(account_id, eth_implicit_test_account_secret())
} else {
InMemorySigner::from_seed(account_id, KeyType::ED25519, account_name.as_str())
}
Expand All @@ -573,6 +575,16 @@ pub fn near_implicit_test_account_secret() -> SecretKey {
"ed25519:5roj6k68kvZu3UEJFyXSfjdKGrodgZUfFLZFpzYXWtESNsLWhYrq3JGi4YpqeVKuw1m9R2TEHjfgWT1fjUqB1DNy".parse().unwrap()
}

/// A fixed ETH-implicit account for which tests can know the private key.
pub fn eth_implicit_test_account() -> AccountId {
"0x96791e923f8cf697ad9c3290f2c9059f0231b24c".parse().unwrap()
}

/// Private key for the fixed ETH-implicit test account.
pub fn eth_implicit_test_account_secret() -> SecretKey {
"secp256k1:X4ETFKtQkSGVoZEnkn7bZ3LyajJaK2b3eweXaKmynGx".parse().unwrap()
}

impl FinalExecutionOutcomeView {
#[track_caller]
/// Check transaction and all transitive receipts for success status.
Expand Down
27 changes: 21 additions & 6 deletions core/primitives/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::version::{
CREATE_RECEIPT_ID_SWITCH_TO_CURRENT_BLOCK_VERSION,
};

use near_crypto::ED25519PublicKey;
use near_crypto::{KeyType, PublicKey};
use near_primitives_core::account::id::AccountId;

use std::mem::size_of;
Expand Down Expand Up @@ -469,23 +469,38 @@ where
Serializable(object)
}

/// Derives `AccountId` from `PublicKey``.
/// Derives `AccountId` from `PublicKey`.
/// If the key type is ED25519, returns hex-encoded copy of the key.
pub fn derive_near_implicit_account_id(public_key: &ED25519PublicKey) -> AccountId {
hex::encode(public_key).parse().unwrap()
/// If the key type is SECP256K1, returns '0x' + keccak256(public_key)[12:32].hex().
pub fn derive_account_id_from_public_key(public_key: &PublicKey) -> AccountId {
match public_key.key_type() {
KeyType::ED25519 => hex::encode(public_key.key_data()).parse().unwrap(),
KeyType::SECP256K1 => {
use sha3::Digest;
let pk_hash = sha3::Keccak256::digest(&public_key.key_data());
format!("0x{}", hex::encode(&pk_hash[12..32])).parse().unwrap()
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use near_crypto::{KeyType, PublicKey};

#[test]
fn test_derive_account_id_from_ed25519_public_key() {
let public_key = PublicKey::from_seed(KeyType::ED25519, "test");
let expected: AccountId =
"bb4dc639b212e075a751685b26bdcea5920a504181ff2910e8549742127092a0".parse().unwrap();
let account_id = derive_near_implicit_account_id(public_key.unwrap_as_ed25519());
let account_id = derive_account_id_from_public_key(&public_key);
assert_eq!(account_id, expected);
}

#[test]
fn test_derive_account_id_from_secp256k1_public_key() {
let public_key = PublicKey::from_seed(KeyType::SECP256K1, "test");
let expected: AccountId = "0x96791e923f8cf697ad9c3290f2c9059f0231b24c".parse().unwrap();
let account_id = derive_account_id_from_public_key(&public_key);
assert_eq!(account_id, expected);
}

Expand Down
Loading

0 comments on commit f61f68a

Please sign in to comment.