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 1, 2023
1 parent 330fb76 commit 6e1c7b1
Show file tree
Hide file tree
Showing 22 changed files with 331 additions and 116 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.

3 changes: 1 addition & 2 deletions core/account-id/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ impl AccountType {
pub fn is_implicit(&self) -> bool {
match &self {
Self::NearImplicitAccount => true,
// TODO(eth-implicit) change to true later, see https://github.com/near/nearcore/issues/10018
Self::EthImplicitAccount => false,
Self::EthImplicitAccount => true,
Self::NamedAccount => false,
}
}
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
19 changes: 10 additions & 9 deletions core/primitives-core/src/runtime/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,11 @@ 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 @@ -235,23 +238,21 @@ pub fn transfer_send_fee(
receiver_account_type: AccountType,
) -> Gas {
let transfer_fee = cfg.fee(ActionCosts::transfer).send_fee(sender_is_receiver);

match (implicit_account_creation_allowed, receiver_account_type) {
// Regular transfer to a named account.
(_, 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) => {
<<<<<<< HEAD
transfer_fee
+ cfg.fee(ActionCosts::create_account).send_fee(sender_is_receiver)
+ cfg.fee(ActionCosts::add_full_access_key).send_fee(sender_is_receiver)
=======
transfer_fee + create_account_fee + add_access_key_fee
>>>>>>> a96103cbc (Style fix for cargo fmt check)
}
}
}
1 change: 1 addition & 0 deletions core/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,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
13 changes: 10 additions & 3 deletions core/primitives/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,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 @@ -485,8 +487,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 @@ -609,7 +611,12 @@ 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
26 changes: 21 additions & 5 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 @@ -471,24 +471,40 @@ where

/// 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();
assert_eq!(derive_account_id_from_public_key(&public_key), expected);
}

#[test]
fn test_num_chunk_producers() {
for num_seats in 1..50 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use near_primitives::shard_layout::ShardLayout;
use near_primitives::sharding::ChunkHash;
use near_primitives::transaction::SignedTransaction;
use near_primitives::types::{AccountId, BlockHeight};
use near_primitives::utils::derive_near_implicit_account_id;
use near_primitives::utils::derive_account_id_from_public_key;
use near_primitives::version::{ProtocolFeature, ProtocolVersion};
use near_primitives::views::FinalExecutionStatus;
use nearcore::config::GenesisExt;
Expand Down Expand Up @@ -200,11 +200,11 @@ fn get_status_of_tx_hash_collision_for_implicit_account(

/// Test that duplicate transactions from NEAR-implicit accounts are properly rejected.
#[test]
fn test_transaction_hash_collision_for_implicit_account_fail() {
fn test_transaction_hash_collision_for_near_implicit_account_fail() {
let protocol_version = ProtocolFeature::AccessKeyNonceForImplicitAccounts.protocol_version();
let secret_key = SecretKey::from_seed(KeyType::ED25519, "test");
let implicit_account_id =
derive_near_implicit_account_id(secret_key.public_key().unwrap_as_ed25519());
derive_account_id_from_public_key(&secret_key.public_key());
let implicit_account_signer = InMemorySigner::from_secret_key(implicit_account_id, secret_key);
assert_matches!(
get_status_of_tx_hash_collision_for_implicit_account(
Expand All @@ -215,14 +215,45 @@ fn test_transaction_hash_collision_for_implicit_account_fail() {
);
}

/// Test that duplicate transactions from ETH-implicit accounts are properly rejected.
#[test]
fn test_transaction_hash_collision_for_eth_implicit_account_fail() {
let protocol_version = ProtocolFeature::AccessKeyNonceForImplicitAccounts.protocol_version();
let secret_key = SecretKey::from_seed(KeyType::SECP256K1, "test");
let implicit_account_id = derive_account_id_from_public_key(&secret_key.public_key());
let implicit_account_signer = InMemorySigner::from_secret_key(implicit_account_id, secret_key);
assert_matches!(
get_status_of_tx_hash_collision_for_implicit_account(protocol_version, implicit_account_signer),
ProcessTxResponse::InvalidTx(InvalidTxError::InvalidNonce { .. })
);
}

/// Test that duplicate transactions from NEAR-implicit accounts are not rejected until protocol upgrade.
#[test]
fn test_transaction_hash_collision_for_implicit_account_ok() {
fn test_transaction_hash_collision_for_near_implicit_account_ok() {
let protocol_version =
ProtocolFeature::AccessKeyNonceForImplicitAccounts.protocol_version() - 1;
let secret_key = SecretKey::from_seed(KeyType::ED25519, "test");
let implicit_account_id =
derive_near_implicit_account_id(secret_key.public_key().unwrap_as_ed25519());
derive_account_id_from_public_key(&secret_key.public_key());
let implicit_account_signer = InMemorySigner::from_secret_key(implicit_account_id, secret_key);
assert_matches!(
get_status_of_tx_hash_collision_for_implicit_account(
protocol_version,
implicit_account_signer
),
ProcessTxResponse::ValidTx
);
}

/// Test that duplicate transactions from ETH-implicit accounts are not rejected until protocol upgrade.
#[test]
fn test_transaction_hash_collision_for_eth_implicit_account_ok() {
let protocol_version =
ProtocolFeature::AccessKeyNonceForImplicitAccounts.protocol_version() - 1;
let secret_key = SecretKey::from_seed(KeyType::SECP256K1, "test");
let implicit_account_id =
derive_account_id_from_public_key(&secret_key.public_key());
let implicit_account_signer = InMemorySigner::from_secret_key(implicit_account_id, secret_key);
assert_matches!(
get_status_of_tx_hash_collision_for_implicit_account(
Expand Down
50 changes: 37 additions & 13 deletions integration-tests/src/tests/client/features/delegate_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ use near_primitives::errors::{
ActionError, ActionErrorKind, ActionsValidationError, InvalidAccessKeyError, InvalidTxError,
TxExecutionError,
};
use near_primitives::test_utils::{create_user_test_signer, near_implicit_test_account};
use near_primitives::test_utils::{create_user_test_signer, eth_implicit_test_account,
near_implicit_test_account};
use near_primitives::transaction::{
Action, AddKeyAction, CreateAccountAction, DeleteAccountAction, DeleteKeyAction,
DeployContractAction, FunctionCallAction, StakeAction, TransferAction,
Expand Down Expand Up @@ -135,12 +136,17 @@ fn check_meta_tx_execution(
.get_access_key(&relayer, &PublicKey::from_seed(KeyType::ED25519, &relayer))
.unwrap()
.nonce;

let user_pubk = match sender.get_account_type() {
AccountType::NearImplicitAccount => PublicKey::from_near_implicit_account(&sender).unwrap(),
AccountType::EthImplicitAccount => PublicKey::from_seed(KeyType::SECP256K1, &sender),
AccountType::NamedAccount => PublicKey::from_seed(KeyType::ED25519, &sender),
AccountType::NearImplicitAccount => Some(PublicKey::from_near_implicit_account(&sender).unwrap()),
// Cannot infer public key from ETH-implicit address, because ETH-implicit address itself is inferred from the public key.
// However, it is for testing purposes only (checking nonce, that might not exist for ETH-implicit account anyway).
AccountType::EthImplicitAccount => None,
AccountType::NamedAccount => Some(PublicKey::from_seed(KeyType::ED25519, &sender)),
};
let user_nonce_before = node_user.get_access_key(&sender, &user_pubk).unwrap().nonce;
let user_nonce_before = user_pubk.map(|user_pubk|
node_user.get_access_key(&sender, &user_pubk).unwrap().nonce
);

let tx_result =
node_user.meta_tx(sender.clone(), receiver.clone(), relayer.clone(), actions).unwrap();
Expand All @@ -154,12 +160,15 @@ fn check_meta_tx_execution(
.unwrap()
.nonce;
assert_eq!(relayer_nonce, relayer_nonce_before + 1);
// user key must be checked for existence (to test DeleteKey action)
if let Ok(user_nonce) = node_user
.get_access_key(&sender, &PublicKey::from_seed(KeyType::ED25519, &sender))
.map(|key| key.nonce)
{
assert_eq!(user_nonce, user_nonce_before + 1);

if let Some(user_nonce_before) = user_nonce_before {
// user key must be checked for existence (to test DeleteKey action)
if let Ok(user_nonce) = node_user
.get_access_key(&sender, &PublicKey::from_seed(KeyType::ED25519, &sender))
.map(|key| key.nonce)
{
assert_eq!(user_nonce, user_nonce_before + 1);
}
}

let sender_after = node_user.view_balance(&sender).unwrap_or(0);
Expand Down Expand Up @@ -803,6 +812,11 @@ fn meta_tx_create_near_implicit_account_fails() {
meta_tx_create_implicit_account_fails(near_implicit_test_account());
}

#[test]
fn meta_tx_create_eth_implicit_account_fails() {
meta_tx_create_implicit_account_fails(eth_implicit_test_account());
}

/// Try creating an implicit account with a meta tx transfer and use the account
/// in the same meta transaction.
///
Expand Down Expand Up @@ -840,6 +854,11 @@ fn meta_tx_create_and_use_near_implicit_account() {
meta_tx_create_and_use_implicit_account(near_implicit_test_account());
}

#[test]
fn meta_tx_create_and_use_eth_implicit_account() {
meta_tx_create_and_use_implicit_account(eth_implicit_test_account());
}

/// Creating an implicit account with a meta tx transfer and use the account in
/// a second meta transaction.
///
Expand All @@ -860,8 +879,8 @@ fn meta_tx_create_implicit_account(new_account: AccountId) {

let tx_cost = match new_account.get_account_type() {
AccountType::NearImplicitAccount => fee_helper.create_account_transfer_full_key_cost(),
AccountType::EthImplicitAccount => panic!("must be near-implicit"),
AccountType::NamedAccount => panic!("must be near-implicit"),
AccountType::EthImplicitAccount => fee_helper.create_account_transfer_cost(),
AccountType::NamedAccount => panic!("must be implicit"),
};
check_meta_tx_no_fn_call(
&node,
Expand Down Expand Up @@ -903,3 +922,8 @@ fn meta_tx_create_implicit_account(new_account: AccountId) {
fn meta_tx_create_near_implicit_account() {
meta_tx_create_implicit_account(near_implicit_test_account());
}

#[test]
fn meta_tx_create_eth_implicit_account() {
meta_tx_create_implicit_account(eth_implicit_test_account());
}
8 changes: 4 additions & 4 deletions integration-tests/src/tests/client/features/restrict_tla.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ fn test_create_top_level_accounts() {
.build();

// These accounts cannot be created because they are top level accounts that are not implicit.
// Note that implicit accounts have to be 64 characters long.
// Note that implicit accounts have to be 64 or 42 (if starts with '0x') characters long.
let top_level_accounts = [
"0x06012c8cf97bead5deae237070f9587f8e7a266d",
"0x5e97870f263700f46aa00d967821199b9bc5a120",
"0x0000000000000000000000000000000000000000",
"0x06012c8cf97bead5deae237070f9587f8e7a266da",
"0a5e97870f263700f46aa00d967821199b9bc5a120",
"0x000000000000000000000000000000000000000",
"alice",
"thisisaveryverylongtoplevelaccount",
];
Expand Down
Loading

0 comments on commit 6e1c7b1

Please sign in to comment.