Skip to content

Commit

Permalink
NEP-364 (#7165)
Browse files Browse the repository at this point in the history
Implementing the following host functions according to the design proposed in [NEP-364](near/NEPs#364)

```rs
    pub fn ed25519_verify(
        &mut self,
        sig_len: u64,
        sig_ptr: u64,
        msg_len: u64,
        msg_ptr: u64,
        pub_key_len: u64,
        pub_key_ptr: u64,
        register_id: u64,
    ) -> Result<()>;
```
- [x] added unit tests
- [x] added costs (this needs to be reviewed, since I'm not sure how to infer the right numbers)
  • Loading branch information
blasrodri authored Sep 6, 2022
1 parent a34992e commit ea96faf
Show file tree
Hide file tree
Showing 22 changed files with 304 additions and 4 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.

10 changes: 9 additions & 1 deletion chain/jsonrpc/res/rpc_errors_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@
"msg": ""
}
},
"Ed25519VerifyInvalidInput": {
"name": "Ed25519VerifyInvalidInput",
"subtypes": [],
"props": {
"msg": ""
}
},
"EmptyMethodName": {
"name": "EmptyMethodName",
"subtypes": [],
Expand Down Expand Up @@ -147,7 +154,8 @@
"ContractSizeExceeded",
"Deprecated",
"ECRecoverError",
"AltBn128InvalidInput"
"AltBn128InvalidInput",
"Ed25519VerifyInvalidInput"
],
"props": {}
},
Expand Down
2 changes: 2 additions & 0 deletions core/primitives-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ serde_json = "1"

[features]
default = []
protocol_feature_ed25519_verify = []

deepsize_feature = [
"deepsize",
"near-account-id/deepsize_feature",
Expand Down
23 changes: 23 additions & 0 deletions core/primitives-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,13 @@ pub struct ExtCostsConfig {
/// Cost of getting ripemd160 per message block
pub ripemd160_block: Gas,

/// Cost of getting ed25519 base
#[cfg(feature = "protocol_feature_ed25519_verify")]
pub ed25519_verify_base: Gas,
/// Cost of getting ed25519 per byte
#[cfg(feature = "protocol_feature_ed25519_verify")]
pub ed25519_verify_byte: Gas,

/// Cost of calling ecrecover
pub ecrecover_base: Gas,

Expand Down Expand Up @@ -452,6 +459,10 @@ impl ExtCostsConfig {
keccak512_base: SAFETY_MULTIPLIER * 1937129412,
keccak512_byte: SAFETY_MULTIPLIER * 12216567,
ripemd160_base: SAFETY_MULTIPLIER * 284558362,
#[cfg(feature = "protocol_feature_ed25519_verify")]
ed25519_verify_base: SAFETY_MULTIPLIER * 1513656750,
#[cfg(feature = "protocol_feature_ed25519_verify")]
ed25519_verify_byte: SAFETY_MULTIPLIER * 7157035,
// Cost per byte is 3542227. There are 64 bytes in a block.
ripemd160_block: SAFETY_MULTIPLIER * 226702528,
ecrecover_base: SAFETY_MULTIPLIER * 1121789875000,
Expand Down Expand Up @@ -520,6 +531,10 @@ impl ExtCostsConfig {
keccak512_byte: 0,
ripemd160_base: 0,
ripemd160_block: 0,
#[cfg(feature = "protocol_feature_ed25519_verify")]
ed25519_verify_base: 0,
#[cfg(feature = "protocol_feature_ed25519_verify")]
ed25519_verify_byte: 0,
ecrecover_base: 0,
log_base: 0,
log_byte: 0,
Expand Down Expand Up @@ -589,6 +604,10 @@ pub enum ExtCosts {
keccak512_byte,
ripemd160_base,
ripemd160_block,
#[cfg(feature = "protocol_feature_ed25519_verify")]
ed25519_verify_base,
#[cfg(feature = "protocol_feature_ed25519_verify")]
ed25519_verify_byte,
ecrecover_base,
log_base,
log_byte,
Expand Down Expand Up @@ -670,6 +689,10 @@ impl ExtCosts {
keccak512_byte => config.keccak512_byte,
ripemd160_base => config.ripemd160_base,
ripemd160_block => config.ripemd160_block,
#[cfg(feature = "protocol_feature_ed25519_verify")]
ed25519_verify_base => config.ed25519_verify_base,
#[cfg(feature = "protocol_feature_ed25519_verify")]
ed25519_verify_byte => config.ed25519_verify_byte,
ecrecover_base => config.ecrecover_base,
log_base => config.log_base,
log_byte => config.log_byte,
Expand Down
4 changes: 4 additions & 0 deletions core/primitives-core/src/parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ pub enum Parameter {
WasmRipemd160Base,
WasmRipemd160Block,
WasmEcrecoverBase,
WasmEd25519VerifyBase,
WasmEd25519VerifyByte,
WasmLogBase,
WasmLogByte,
WasmStorageWriteBase,
Expand Down Expand Up @@ -236,6 +238,8 @@ impl Parameter {
Parameter::WasmRipemd160Base,
Parameter::WasmRipemd160Block,
Parameter::WasmEcrecoverBase,
Parameter::WasmEd25519VerifyBase,
Parameter::WasmEd25519VerifyByte,
Parameter::WasmLogBase,
Parameter::WasmLogByte,
Parameter::WasmStorageWriteBase,
Expand Down
8 changes: 8 additions & 0 deletions core/primitives-core/src/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ impl Cost {
Cost::ExtCost { ext_cost_kind: ExtCosts::alt_bn128_pairing_check_element },
Cost::ExtCost { ext_cost_kind: ExtCosts::alt_bn128_g1_sum_base },
Cost::ExtCost { ext_cost_kind: ExtCosts::alt_bn128_g1_sum_element },
#[cfg(feature = "protocol_feature_ed25519_verify")]
Cost::ExtCost { ext_cost_kind: ExtCosts::ed25519_verify_base },
#[cfg(feature = "protocol_feature_ed25519_verify")]
Cost::ExtCost { ext_cost_kind: ExtCosts::ed25519_verify_byte },
];

pub fn index(self) -> usize {
Expand Down Expand Up @@ -326,6 +330,10 @@ impl Cost {
Cost::ExtCost { ext_cost_kind: ExtCosts::alt_bn128_pairing_check_element } => 67,
Cost::ExtCost { ext_cost_kind: ExtCosts::alt_bn128_g1_sum_base } => 68,
Cost::ExtCost { ext_cost_kind: ExtCosts::alt_bn128_g1_sum_element } => 69,
#[cfg(feature = "protocol_feature_ed25519_verify")]
Cost::ExtCost { ext_cost_kind: ExtCosts::ed25519_verify_base } => 70,
#[cfg(feature = "protocol_feature_ed25519_verify")]
Cost::ExtCost { ext_cost_kind: ExtCosts::ed25519_verify_byte } => 71,
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion core/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,18 @@ protocol_feature_fix_staking_threshold = []
protocol_feature_fix_contract_loading_cost = []
protocol_feature_account_id_in_function_call_permission = []
protocol_feature_reject_blocks_with_outdated_protocol_version = []
protocol_feature_ed25519_verify = [
"near-primitives-core/protocol_feature_ed25519_verify"
]
nightly = [
"nightly_protocol",
"protocol_feature_fix_staking_threshold",
"protocol_feature_fix_contract_loading_cost",
"protocol_feature_account_id_in_function_call_permission",
"protocol_feature_reject_blocks_with_outdated_protocol_version"
"protocol_feature_reject_blocks_with_outdated_protocol_version",
"protocol_feature_ed25519_verify",
]

nightly_protocol = []


Expand Down
3 changes: 3 additions & 0 deletions core/primitives/res/runtime_configs/parameters.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ wasm_keccak512_byte: 36_649_701
wasm_ripemd160_base: 853_675_086
wasm_ripemd160_block: 680_107_584
wasm_ecrecover_base: 3_365_369_625_000
# both ed25519_verify_base and ed25519_verify_byte have NON-FINAL numbers (needs fine tuning)
wasm_ed25519_verify_base: 40_311_888_867
wasm_ed25519_verify_byte: 423_978_605
wasm_log_base: 3_543_313_050
wasm_log_byte: 13_198_791
wasm_storage_write_base: 64_196_736_000
Expand Down
3 changes: 3 additions & 0 deletions core/primitives/res/runtime_configs/parameters_testnet.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ wasm_keccak512_byte: 36_649_701
wasm_ripemd160_base: 853_675_086
wasm_ripemd160_block: 680_107_584
wasm_ecrecover_base: 3_365_369_625_000
# both ed25519_verify_base and ed25519_verify_byte have NON-FINAL numbers (needs fine tuning)
wasm_ed25519_verify_base: 40_311_888_867
wasm_ed25519_verify_byte: 423_978_605
wasm_log_base: 3_543_313_050
wasm_log_byte: 13_198_791
wasm_storage_write_base: 64_196_736_000
Expand Down
4 changes: 4 additions & 0 deletions core/primitives/src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ pub enum ProtocolFeature {
/// Validate account id for function call access keys.
#[cfg(feature = "protocol_feature_account_id_in_function_call_permission")]
AccountIdInFunctionCallPermission,
#[cfg(feature = "protocol_feature_ed25519_verify")]
Ed25519Verify,
#[cfg(feature = "protocol_feature_reject_blocks_with_outdated_protocol_version")]
RejectBlocksWithOutdatedProtocolVersions,
#[cfg(feature = "shardnet")]
Expand Down Expand Up @@ -258,6 +260,8 @@ impl ProtocolFeature {
ProtocolFeature::FixContractLoadingCost => 129,
#[cfg(feature = "protocol_feature_account_id_in_function_call_permission")]
ProtocolFeature::AccountIdInFunctionCallPermission => 130,
#[cfg(feature = "protocol_feature_ed25519_verify")]
ProtocolFeature::Ed25519Verify => 131,
#[cfg(feature = "protocol_feature_reject_blocks_with_outdated_protocol_version")]
ProtocolFeature::RejectBlocksWithOutdatedProtocolVersions => {
if cfg!(feature = "shardnet") {
Expand Down
44 changes: 44 additions & 0 deletions runtime/near-test-contracts/estimator-contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ extern "C" {
malleability_flag: u64,
register_id: u64,
) -> u64;
pub fn ed25519_verify(
sig_len: u64,
sig_ptr: u64,
msg_len: u64,
msg_ptr: u64,
pub_key_len: u64,
pub_key_ptr: u64,
) -> u64;
// #####################
// # Miscellaneous API #
// #####################
Expand Down Expand Up @@ -467,6 +475,42 @@ pub unsafe fn ecrecover_10k() {
}
}

// Function to measure `ed25519_verify_base`. Also measures `base`, `write_register_base`, and
// `write_register_byte`. However `ed25519_verify_base` computation is more expensive than register writing
// so we are okay overcharging it. // TODO: validate this
// Compute ecrecover 10k times.
#[no_mangle]
pub unsafe fn ed25519_verify_10k() {
let signature: [u8; 64] = [
145, 193, 203, 18, 114, 227, 14, 117, 33, 213, 121, 66, 130, 14, 25, 4, 36, 120, 46, 142,
226, 215, 7, 66, 122, 112, 97, 30, 249, 135, 61, 165, 221, 249, 252, 23, 105, 40, 56, 70,
31, 152, 236, 141, 154, 122, 207, 20, 75, 118, 79, 90, 168, 6, 221, 122, 213, 29, 126, 196,
216, 104, 191, 6,
];

let public_key: [u8; 32] = [
32, 122, 6, 120, 146, 130, 30, 37, 215, 112, 241, 251, 160, 196, 124, 17, 255, 75, 129, 62,
84, 22, 46, 206, 158, 184, 57, 224, 118, 35, 26, 182,
];

// 32 bytes message
let message: [u8; 32] = [
107, 97, 106, 100, 108, 102, 107, 106, 97, 108, 107, 102, 106, 97, 107, 108, 102, 106, 100,
107, 108, 97, 100, 106, 102, 107, 108, 106, 97, 100, 115, 107,
];

for _ in 0..10_000 {
ed25519_verify(
signature.len() as _,
signature.as_ptr() as _,
message.len() as _,
message.as_ptr() as _,
public_key.len() as _,
public_key.as_ptr() as _,
);
}
}

#[repr(C)]
struct MultiexpElem([u8; 64], [u8; 32]);

Expand Down
4 changes: 4 additions & 0 deletions runtime/near-vm-errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ pub enum HostError {
/// Invalid input to alt_bn128 familiy of functions (e.g., point which isn't
/// on the curve).
AltBn128InvalidInput { msg: String },
/// Invalid input to ed25519 signature verification function (e.g. signature cannot be
/// derived from bytes).
Ed25519VerifyInvalidInput { msg: String },
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -421,6 +424,7 @@ impl std::fmt::Display for HostError {
Deprecated {method_name}=> write!(f, "Attempted to call deprecated host function {}", method_name),
AltBn128InvalidInput { msg } => write!(f, "AltBn128 invalid input: {}", msg),
ECRecoverError { msg } => write!(f, "ECDSA recover error: {}", msg),
Ed25519VerifyInvalidInput { msg } => write!(f, "ED25519 signature verification error: {}", msg),
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions runtime/near-vm-logic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ ripemd = "0.1.1"
serde = { version = "1", features = ["derive"] }
sha2 = ">=0.8,<=0.10"
sha3 = ">=0.8,<=0.10"
ed25519-dalek = "1"

near-crypto = { path = "../../core/crypto" }
near-account-id = { path = "../../core/account-id", features = [ "internal_unstable" ] }
Expand All @@ -37,11 +38,15 @@ tracing = { version = "0.1.13", optional = true }
hex = { version = "0.4", features = ["serde"] }
serde_json = { version = "1", features = ["preserve_order"] }


[features]
default = []
protocol_feature_fix_contract_loading_cost = [
"near-primitives/protocol_feature_fix_contract_loading_cost",
]
protocol_feature_ed25519_verify = [
"near-primitives/protocol_feature_ed25519_verify"
]
io_trace = ["tracing"]

# Use this feature to enable counting of fees and costs applied.
Expand Down
52 changes: 52 additions & 0 deletions runtime/near-vm-logic/src/logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,58 @@ impl<'a> VMLogic<'a> {
Ok(false as u64)
}

/// Verify an ED25519 signature given a message and a public key.
/// # Returns
/// - 1 meaning the boolean expression true to encode that the signature was properly verified
/// - 0 meaning the boolean expression false to encode that the signature failed to be verified
///
/// # Cost
///
/// Each input can either be in memory or in a register. Set the length of the input to `u64::MAX`
/// to declare that the input is a register number and not a pointer.
/// Each input has a gas cost input_cost(num_bytes) that depends on whether it is from memory
/// or from a register. It is either read_memory_base + num_bytes * read_memory_byte in the
/// former case or read_register_base + num_bytes * read_register_byte in the latter. This function
/// is labeled as `input_cost` below.
///
/// `input_cost(num_bytes_signature) + input_cost(num_bytes_message) + input_cost(num_bytes_public_key) +
/// ed25519_verify_base + ed25519_verify_byte * num_bytes_message`
#[cfg(feature = "protocol_feature_ed25519_verify")]
pub fn ed25519_verify(
&mut self,
sig_len: u64,
sig_ptr: u64,
msg_len: u64,
msg_ptr: u64,
pub_key_len: u64,
pub_key_ptr: u64,
) -> Result<u64> {
use ed25519_dalek::{PublicKey, Signature, Verifier, SIGNATURE_LENGTH};

self.gas_counter.pay_base(ed25519_verify_base)?;
if sig_len != SIGNATURE_LENGTH as u64 {
return Err(VMLogicError::HostError(HostError::Ed25519VerifyInvalidInput {
msg: "invalid signature length".to_string(),
}));
}
let msg = self.get_vec_from_memory_or_register(msg_ptr, msg_len)?;
let signature_array = self.get_vec_from_memory_or_register(sig_ptr, sig_len)?;
let signature = Signature::from_bytes(&signature_array).map_err(|e| {
VMLogicError::HostError(HostError::Ed25519VerifyInvalidInput { msg: e.to_string() })
})?;
let num_bytes = msg.len();
self.gas_counter.pay_per(ed25519_verify_byte, num_bytes as _)?;

let pub_key_array = self.get_vec_from_memory_or_register(pub_key_ptr, pub_key_len)?;
let pub_key = PublicKey::from_bytes(&pub_key_array).map_err(|e| {
VMLogicError::HostError(HostError::Ed25519VerifyInvalidInput { msg: e.to_string() })
})?;
match pub_key.verify(&msg, &signature) {
Err(_) => Ok(0),
Ok(()) => Ok(1),
}
}

/// Called by gas metering injected into Wasm. Counts both towards `burnt_gas` and `used_gas`.
///
/// # Errors
Expand Down
Loading

0 comments on commit ea96faf

Please sign in to comment.