diff --git a/.gitignore b/.gitignore index 0b30f9765..55f98965f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/target +**/target .direnv .DS_Store .idea diff --git a/.vscode/settings.json b/.vscode/settings.json index 215c4b010..03e1caa0c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,6 @@ "integration-tests/chain-signatures/Cargo.toml", "integration-tests/fastauth/Cargo.toml", "mpc-recovery/Cargo.toml", + "load-tests/Cargo.toml", ], } diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..5d2b06941 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog + +## 0.3.0 + +- Payload hash scalars are now big endian. In general this means that clients of the contract should no longer reverse their hashed payload before sending it to the MPC contract. + diff --git a/chain-signatures/Cargo.lock b/chain-signatures/Cargo.lock index 118e63c63..4870677b1 100644 --- a/chain-signatures/Cargo.lock +++ b/chain-signatures/Cargo.lock @@ -1176,11 +1176,11 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe5b10e214954177fb1dc9fbd20a1a2608fe99e6c832033bdc7cea287a20d77" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" dependencies = [ - "borsh-derive 1.5.0", + "borsh-derive 1.5.1", "cfg_aliases", ] @@ -1199,9 +1199,9 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a8646f94ab393e43e8b35a2558b1624bed28b97ee09c5d15456e3c9463f46d" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" dependencies = [ "once_cell", "proc-macro-crate 3.1.0", @@ -1481,9 +1481,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chacha20" @@ -2000,6 +2000,20 @@ dependencies = [ "subtle", ] +[[package]] +name = "crypto-shared" +version = "0.0.0" +dependencies = [ + "anyhow", + "borsh 1.5.1", + "getrandom 0.2.15", + "k256", + "near-account-id", + "near-sdk", + "serde", + "serde_json", +] + [[package]] name = "csv" version = "1.3.0" @@ -4178,7 +4192,9 @@ name = "mpc-contract" version = "0.2.0" dependencies = [ "anyhow", - "borsh 1.5.0", + "borsh 1.5.1", + "crypto-shared", + "k256", "near-sdk", "near-workspaces", "schemars", @@ -4191,7 +4207,7 @@ dependencies = [ name = "mpc-keys" version = "0.1.0" dependencies = [ - "borsh 1.5.0", + "borsh 1.5.1", "hex 0.4.3", "hpke", "rand 0.8.5", @@ -4212,6 +4228,7 @@ dependencies = [ "cait-sith", "chrono", "clap", + "crypto-shared", "google-datastore1", "google-secretmanager1", "hex 0.4.3", @@ -4284,7 +4301,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c49593c9e94454a2368a4c0a511bf4bf1413aff4d23f16e1d8f4e64b5215351" dependencies = [ - "borsh 1.5.0", + "borsh 1.5.1", "schemars", "semver 1.0.23", "serde", @@ -4336,7 +4353,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35cbb989542587b47205e608324ddd391f0cee1c22b4b64ae49f458334b95907" dependencies = [ - "borsh 1.5.0", + "borsh 1.5.1", "serde", ] @@ -4474,7 +4491,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2991d2912218a80ec0733ac87f84fa803accea105611eea209d4419271957667" dependencies = [ "blake2", - "borsh 1.5.0", + "borsh 1.5.1", "bs58 0.4.0", "c2-chacha", "curve25519-dalek 4.1.2", @@ -4501,7 +4518,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d927e95742aea981b9fd60996fbeba3b61e90acafd54c2c3c2a4ed40065ff03" dependencies = [ "blake2", - "borsh 1.5.0", + "borsh 1.5.1", "bs58 0.4.0", "c2-chacha", "curve25519-dalek 4.1.2", @@ -4565,7 +4582,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14e75c875026229902d065e4435804497337b631ec69ba746b102954273e9ad1" dependencies = [ - "borsh 1.5.0", + "borsh 1.5.1", "interactive-clap", "schemars", "serde", @@ -4588,7 +4605,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18ad81e015f7aced8925d5b9ba3f369b36da9575c15812cfd0786bc1213284ca" dependencies = [ - "borsh 1.5.0", + "borsh 1.5.1", "lazy_static", "log", "near-chain-configs 0.20.1", @@ -4607,7 +4624,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5225c0f97a61fd4534dee3169959dd91bb812be7d0573c1130a3cf86fd16b3e" dependencies = [ - "borsh 1.5.0", + "borsh 1.5.1", "lazy_static", "log", "near-chain-configs 0.21.2", @@ -4763,7 +4780,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9f16a59b6c3e69b0585be951af6fe42a0ba86c0e207cb8c63badd19efd16680" dependencies = [ "assert_matches", - "borsh 1.5.0", + "borsh 1.5.1", "enum-map", "near-account-id", "near-primitives-core 0.20.1", @@ -4782,7 +4799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a996c8654020f7eb3c11039cb39123fd4cd78654fde4c9e7c3fd6d092c84f342" dependencies = [ "assert_matches", - "borsh 1.5.0", + "borsh 1.5.1", "enum-map", "near-account-id", "near-primitives-core 0.21.2", @@ -4802,7 +4819,7 @@ checksum = "0462b067732132babcc89d5577db3bfcb0a1bcfbaaed3f2db4c11cd033666314" dependencies = [ "arbitrary", "base64 0.21.7", - "borsh 1.5.0", + "borsh 1.5.1", "bytesize", "cfg-if 1.0.0", "chrono", @@ -4844,7 +4861,7 @@ checksum = "7c880397c022d3b8f592cef18f85fd6e79181a2a04c31154afb1730f9fa21098" dependencies = [ "arbitrary", "base64 0.21.7", - "borsh 1.5.0", + "borsh 1.5.1", "bytesize", "cfg-if 1.0.0", "chrono", @@ -4886,7 +4903,7 @@ checksum = "8443eb718606f572c438be6321a097a8ebd69f8e48d953885b4f16601af88225" dependencies = [ "arbitrary", "base64 0.21.7", - "borsh 1.5.0", + "borsh 1.5.1", "bs58 0.4.0", "derive_more", "enum-map", @@ -4908,7 +4925,7 @@ checksum = "082b1d3f6c7e273ec5cd9588e00bdbfc51be6cc9a3a7ec31fc899b4b7d2d3f9d" dependencies = [ "arbitrary", "base64 0.21.7", - "borsh 1.5.0", + "borsh 1.5.1", "bs58 0.4.0", "derive_more", "enum-map", @@ -4988,7 +5005,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520234cfdf04a805ac2f04715889d096eb83fdd5b99ca7d0f8027ae473f891a8" dependencies = [ "base64 0.21.7", - "borsh 1.5.0", + "borsh 1.5.1", "bs58 0.5.1", "near-account-id", "near-crypto 0.20.1", @@ -5064,7 +5081,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b68f3f8a2409f72b43efdbeff8e820b81e70824c49fee8572979d789d1683fb" dependencies = [ - "borsh 1.5.0", + "borsh 1.5.1", "interactive-clap", "serde", ] @@ -5140,7 +5157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c56c80bdb1954808f59bd36a9112377197b38d424991383bf05f52d0fe2e0da5" dependencies = [ "base64 0.21.7", - "borsh 1.5.0", + "borsh 1.5.1", "ed25519-dalek 2.1.1", "enum-map", "memoffset 0.8.0", @@ -5171,7 +5188,7 @@ checksum = "20569500ca56e161c6ed81da9a24c7bf7b974c4238b2f08b2582113b66fa0060" dependencies = [ "anyhow", "base64 0.21.7", - "borsh 1.5.0", + "borsh 1.5.1", "ed25519-dalek 2.1.1", "enum-map", "finite-wasm", diff --git a/chain-signatures/contract/Cargo.toml b/chain-signatures/contract/Cargo.toml index 0aef17372..57124c8f5 100644 --- a/chain-signatures/contract/Cargo.toml +++ b/chain-signatures/contract/Cargo.toml @@ -12,6 +12,8 @@ near-sdk = { version = "=5.1.0", features = ["legacy", "unit-testing"] } serde = { version = "1", features = ["derive"] } serde_json = "1" schemars = "0.8" +k256 = { version = "0.13.1", features = ["sha256", "ecdsa", "serde", "arithmetic", "expose-field"] } +crypto-shared = { path = "../../crypto-shared" } [dev-dependencies] near-workspaces = { git = "https://github.com/near/near-workspaces-rs.git", branch = "feat/upgrade-near-deps" } diff --git a/chain-signatures/contract/src/lib.rs b/chain-signatures/contract/src/lib.rs index 317901063..0371a0d86 100644 --- a/chain-signatures/contract/src/lib.rs +++ b/chain-signatures/contract/src/lib.rs @@ -1,12 +1,20 @@ pub mod primitives; +use crypto_shared::{ + derive_epsilon, derive_key, kdf::check_ec_signature, near_public_key_to_affine_point, + types::SignatureResponse, ScalarExt as _, SerializableScalar, +}; use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; use near_sdk::collections::LookupMap; use near_sdk::serde::{Deserialize, Serialize}; + use near_sdk::{ env, log, near_bindgen, AccountId, BorshStorageKey, Gas, Promise, PromiseOrValue, PublicKey, }; -use primitives::{CandidateInfo, Candidates, ParticipantInfo, Participants, PkVotes, Votes}; + +use primitives::{ + CandidateInfo, Candidates, ParticipantInfo, Participants, PkVotes, SignRequest, Votes, +}; use std::collections::{BTreeMap, HashSet}; const GAS_FOR_SIGN_CALL: Gas = Gas::from_tgas(250); @@ -65,38 +73,55 @@ impl Default for VersionedMpcContract { } } +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone)] +pub struct SignatureRequest { + pub epsilon: SerializableScalar, + pub payload_hash: [u8; 32], +} + +impl SignatureRequest { + pub fn new(payload_hash: [u8; 32], predecessor_id: &AccountId, path: &str) -> Self { + let scalar = derive_epsilon(predecessor_id, path); + let epsilon = SerializableScalar { scalar }; + SignatureRequest { + epsilon, + payload_hash, + } + } +} + #[derive(BorshDeserialize, BorshSerialize, Debug)] pub struct MpcContract { protocol_state: ProtocolContractState, - pending_requests: LookupMap<[u8; 32], Option<(String, String)>>, + pending_requests: LookupMap>, request_counter: u32, } impl MpcContract { - fn add_request(&mut self, payload: &[u8; 32], signature: &Option<(String, String)>) { + fn add_request(&mut self, request: &SignatureRequest, result: &Option) { if self.request_counter > 8 { env::panic_str("Too many pending requests. Please, try again later."); } - if !self.pending_requests.contains_key(payload) { + if !self.pending_requests.contains_key(request) { self.request_counter += 1; } - self.pending_requests.insert(payload, signature); + self.pending_requests.insert(request, result); } - fn remove_request(&mut self, payload: &[u8; 32]) { + fn remove_request(&mut self, payload: &SignatureRequest) { self.pending_requests.remove(payload); self.request_counter -= 1; } - fn add_signature(&mut self, payload: &[u8; 32], signature: (String, String)) { + fn add_sign_result(&mut self, payload: &SignatureRequest, signature: SignatureResponse) { if self.pending_requests.contains_key(payload) { self.pending_requests.insert(payload, &Some(signature)); } } - fn clean_payloads(&mut self, payloads: Vec<[u8; 32]>, counter: u32) { + fn clean_payloads(&mut self, requests: Vec, counter: u32) { log!("clean_payloads"); - for payload in payloads.iter() { + for payload in requests.iter() { self.pending_requests.remove(payload); } self.request_counter = counter; @@ -120,7 +145,12 @@ impl MpcContract { impl VersionedMpcContract { #[allow(unused_variables)] /// `key_version` must be less than or equal to the value at `latest_key_version` - pub fn sign(&mut self, payload: [u8; 32], path: String, key_version: u32) -> Promise { + pub fn sign(&mut self, request: SignRequest) -> Promise { + let SignRequest { + payload, + path, + key_version, + } = request; let latest_key_version: u32 = self.latest_key_version(); assert!( key_version <= latest_key_version, @@ -134,25 +164,28 @@ impl VersionedMpcContract { env::prepaid_gas(), GAS_FOR_SIGN_CALL ); + let predecessor = env::predecessor_account_id(); log!( - "sign: signer={}, payload={:?}, path={:?}, key_version={}", - env::signer_account_id(), + "sign: predecessor={}, payload={:?}, path={:?}, key_version={}", + predecessor, payload, path, key_version ); - match self.signature_per_payload(payload) { + + let request = SignatureRequest::new(payload, &predecessor, &path); + match self.sign_result(&request) { None => { - self.add_request(&payload, &None); + self.add_sign_request(&request); log!(&serde_json::to_string(&near_sdk::env::random_seed_array()).unwrap()); - Self::ext(env::current_account_id()).sign_helper(payload, 0) + Self::ext(env::current_account_id()).sign_helper(request, 0) } Some(_) => env::panic_str("Signature for this payload already requested"), } } /// This is the root public key combined from all the public keys of the participants. - pub fn public_key(self) -> PublicKey { + pub fn public_key(&self) -> PublicKey { match self.state() { ProtocolContractState::Running(state) => state.public_key.clone(), ProtocolContractState::Resharing(state) => state.public_key.clone(), @@ -172,27 +205,41 @@ impl VersionedMpcContract { pub fn version(&self) -> String { env!("CARGO_PKG_VERSION").to_string() } -} -// MPC Node API -#[near_bindgen] -impl VersionedMpcContract { - pub fn respond(&mut self, payload: [u8; 32], big_r: String, s: String) { + pub fn respond(&mut self, request: SignatureRequest, response: SignatureResponse) { let protocol_state = self.mutable_state(); - if let ProtocolContractState::Running(state) = protocol_state { + if let ProtocolContractState::Running(_) = protocol_state { let signer = env::signer_account_id(); - if state.participants.contains_key(&signer) { - log!( - "respond: signer={}, payload={:?} big_r={} s={}", - signer, - payload, - big_r, - s - ); - self.add_signature(&payload, (big_r, s)); - } else { - env::panic_str("only participants can respond"); + // TODO add back in a check to see that the caller is a participant (it's horrible to test atm) + // It's not strictly necessary, since we verify the payload is correct + log!( + "respond: signer={}, request={:?} big_r={:?} s={:?}", + &signer, + &request, + &response.big_r, + &response.s + ); + + // generate the expected public key + let expected_public_key = derive_key( + near_public_key_to_affine_point(self.public_key()), + request.epsilon.scalar, + ); + + // Check the signature is correct + if check_ec_signature( + &expected_public_key, + &response.big_r.affine_point, + &response.s.scalar, + k256::Scalar::from_bytes(&request.payload_hash[..]), + response.recovery_id, + ) + .is_err() + { + env::panic_str("Signature could not be verified"); } + + self.add_sign_result(&request, response); } else { env::panic_str("protocol is not in a running state"); } @@ -470,10 +517,10 @@ impl VersionedMpcContract { #[private] pub fn sign_helper( &mut self, - payload: [u8; 32], + request: SignatureRequest, depth: usize, - ) -> PromiseOrValue<(String, String)> { - if let Some(signature) = self.signature_per_payload(payload) { + ) -> PromiseOrValue { + if let Some(signature) = self.sign_result(&request) { match signature { Some(signature) => { log!( @@ -481,7 +528,7 @@ impl VersionedMpcContract { signature, depth ); - self.remove_request(&payload); + self.remove_sign_request(&request); PromiseOrValue::Value(signature) } None => { @@ -490,7 +537,7 @@ impl VersionedMpcContract { // We keep one call back so we can cleanup then call panic on the next call // Start cleaning up if there's less than 25 teragas left regardless of how deep you are. if depth > 30 || env::prepaid_gas() < Gas::from_tgas(25) { - self.remove_request(&payload); + self.remove_sign_request(&request); let self_id = env::current_account_id(); PromiseOrValue::Promise(Self::ext(self_id).fail_helper( "Signature was not provided in time. Please, try again.".to_string(), @@ -502,7 +549,7 @@ impl VersionedMpcContract { )); let account_id = env::current_account_id(); PromiseOrValue::Promise( - Self::ext(account_id).sign_helper(payload, depth + 1), + Self::ext(account_id).sign_helper(request, depth + 1), ) } } @@ -518,9 +565,9 @@ impl VersionedMpcContract { env::panic_str(&message); } - pub fn state(self) -> ProtocolContractState { + pub fn state(&self) -> &ProtocolContractState { match self { - Self::V0(mpc_contract) => mpc_contract.protocol_state, + Self::V0(mpc_contract) => &mpc_contract.protocol_state, } } @@ -539,10 +586,10 @@ impl VersionedMpcContract { } #[private] - pub fn clean_payloads(&mut self, payloads: Vec<[u8; 32]>, counter: u32) { + pub fn clean_payloads(&mut self, requests: Vec, counter: u32) { match self { Self::V0(mpc_contract) => { - mpc_contract.clean_payloads(payloads, counter); + mpc_contract.clean_payloads(requests, counter); } } } @@ -557,31 +604,27 @@ impl VersionedMpcContract { request_counter: old_contract.request_counter, }) } -} -// Helper functions -#[near_bindgen] -impl VersionedMpcContract { - fn remove_request(&mut self, payload: &[u8; 32]) { + fn remove_sign_request(&mut self, request: &SignatureRequest) { match self { Self::V0(mpc_contract) => { - mpc_contract.remove_request(payload); + mpc_contract.remove_request(request); } } } - fn add_request(&mut self, payload: &[u8; 32], signature: &Option<(String, String)>) { + fn add_sign_request(&mut self, request: &SignatureRequest) { match self { Self::V0(mpc_contract) => { - mpc_contract.add_request(payload, signature); + mpc_contract.add_request(request, &None); } } } - fn add_signature(&mut self, payload: &[u8; 32], signature: (String, String)) { + fn add_sign_result(&mut self, request: &SignatureRequest, response: SignatureResponse) { match self { Self::V0(mpc_contract) => { - mpc_contract.add_signature(payload, signature); + mpc_contract.add_sign_result(request, response); } } } @@ -592,9 +635,15 @@ impl VersionedMpcContract { } } - fn signature_per_payload(&self, payload: [u8; 32]) -> Option> { + fn sign_result(&self, request: &SignatureRequest) -> Option> { match self { - Self::V0(mpc_contract) => mpc_contract.pending_requests.get(&payload), + Self::V0(mpc_contract) => mpc_contract.pending_requests.get(request), } } + + // fn public_key(&self) -> String { + // match self { + // Self::V0(mpc_contract) => mpc_contract + // } + // } } diff --git a/chain-signatures/contract/src/primitives.rs b/chain-signatures/contract/src/primitives.rs index da64800b6..5427032f9 100644 --- a/chain-signatures/contract/src/primitives.rs +++ b/chain-signatures/contract/src/primitives.rs @@ -207,3 +207,16 @@ impl PkVotes { self.votes.entry(public_key).or_default() } } + +#[derive(Serialize, Deserialize, BorshDeserialize, BorshSerialize, Debug)] +pub struct SignRequest { + pub payload: [u8; 32], + pub path: String, + pub key_version: u32, +} + +#[derive(Serialize, Deserialize, BorshDeserialize, BorshSerialize, Debug)] +pub struct SignResult { + pub big_r: String, + pub s: String, +} diff --git a/chain-signatures/node/Cargo.toml b/chain-signatures/node/Cargo.toml index f5b79e916..7cb90e709 100644 --- a/chain-signatures/node/Cargo.toml +++ b/chain-signatures/node/Cargo.toml @@ -51,6 +51,7 @@ near-sdk = { version = "=5.1.0", features = ["legacy", "unit-testing"] } mpc-contract = { path = "../contract" } mpc-keys = { path = "../keys" } +crypto-shared = { path = "../../crypto-shared" } itertools = "0.12.0" prometheus = { version = "0.13.3" } diff --git a/chain-signatures/node/src/indexer.rs b/chain-signatures/node/src/indexer.rs index 02b339fc0..73b78ca6f 100644 --- a/chain-signatures/node/src/indexer.rs +++ b/chain-signatures/node/src/indexer.rs @@ -2,7 +2,7 @@ use crate::gcp::GcpService; use crate::kdf; use crate::protocol::{SignQueue, SignRequest}; use crate::types::LatestBlockHeight; - +use crypto_shared::derive_epsilon; use near_account_id::AccountId; use near_lake_framework::{LakeBuilder, LakeContext}; use near_lake_primitives::actions::ActionMetaDataExt; @@ -66,11 +66,16 @@ impl Options { } } -#[derive(Debug, Serialize, Deserialize)] -struct SignPayload { - payload: [u8; 32], - path: String, - key_version: u32, +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct SignArguments { + pub request: ContractSignRequest, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct ContractSignRequest { + pub payload: [u8; 32], + pub path: String, + pub key_version: u32, } #[derive(LakeContext)] @@ -100,8 +105,8 @@ async fn handle_block( }; if let Some(function_call) = action.as_function_call() { if function_call.method_name() == "sign" { - if let Ok(sign_payload) = - serde_json::from_slice::<'_, SignPayload>(function_call.args()) + if let Ok(arguments) = + serde_json::from_slice::<'_, SignArguments>(function_call.args()) { if receipt.logs().is_empty() { tracing::warn!("`sign` did not produce entropy"); @@ -116,21 +121,21 @@ async fn handle_block( continue; }; let epsilon = - kdf::derive_epsilon(&action.predecessor_id(), &sign_payload.path); + derive_epsilon(&action.predecessor_id(), &arguments.request.path); let delta = kdf::derive_delta(receipt_id, entropy); tracing::info!( receipt_id = %receipt_id, caller_id = receipt.predecessor_id().to_string(), our_account = ctx.node_account_id.to_string(), - payload = hex::encode(sign_payload.payload), - key_version = sign_payload.key_version, + payload = hex::encode(arguments.request.payload), + key_version = arguments.request.key_version, entropy = hex::encode(entropy), "indexed new `sign` function call" ); let mut queue = ctx.queue.write().await; queue.add(SignRequest { receipt_id, - msg_hash: sign_payload.payload, + request: arguments.request, epsilon, delta, entropy, diff --git a/chain-signatures/node/src/kdf.rs b/chain-signatures/node/src/kdf.rs index 54318689d..99700459d 100644 --- a/chain-signatures/node/src/kdf.rs +++ b/chain-signatures/node/src/kdf.rs @@ -1,36 +1,13 @@ -use crate::types::PublicKey; -use crate::util::ScalarExt; use anyhow::Context; -use cait_sith::FullSignature; +use crypto_shared::{x_coordinate, ScalarExt, SignatureResponse}; use hkdf::Hkdf; -use k256::ecdsa::{RecoveryId, VerifyingKey}; -use k256::elliptic_curve::point::AffineCoordinates; -use k256::elliptic_curve::sec1::ToEncodedPoint; -use k256::elliptic_curve::CurveArithmetic; -use k256::{AffinePoint, Scalar, Secp256k1}; -use near_account_id::AccountId; +use k256::{ + ecdsa::{RecoveryId, VerifyingKey}, + elliptic_curve::sec1::ToEncodedPoint, + Scalar, +}; use near_primitives::hash::CryptoHash; -use sha2::{Digest, Sha256}; - -// Constant prefix that ensures epsilon derivation values are used specifically for -// near-mpc-recovery with key derivation protocol vX.Y.Z. -const EPSILON_DERIVATION_PREFIX: &str = "near-mpc-recovery v0.1.0 epsilon derivation:"; -// Constant prefix that ensures delta derivation values are used specifically for -// near-mpc-recovery with key derivation protocol vX.Y.Z. -const DELTA_DERIVATION_PREFIX: &str = "near-mpc-recovery v0.1.0 delta derivation:"; - -pub fn derive_epsilon(signer_id: &AccountId, path: &str) -> Scalar { - // TODO: Use a key derivation library instead of doing this manually. - // https://crates.io/crates/hkdf might be a good option? - // - // ',' is ACCOUNT_DATA_SEPARATOR from nearcore that indicate the end - // of the accound id in the trie key. We reuse the same constant to - // indicate the end of the account id in derivation path. - let derivation_path = format!("{EPSILON_DERIVATION_PREFIX}{},{}", signer_id, path); - let mut hasher = Sha256::new(); - hasher.update(derivation_path); - Scalar::from_bytes(&hasher.finalize()) -} +use sha2::Sha256; // In case there are multiple requests in the same block (hence same entropy), we need to ensure // that we generate different random scalars as delta tweaks. @@ -43,27 +20,20 @@ pub fn derive_delta(receipt_id: CryptoHash, entropy: [u8; 32]) -> Scalar { Scalar::from_bytes(&okm) } -pub fn derive_key(public_key: PublicKey, epsilon: Scalar) -> PublicKey { - (::ProjectivePoint::GENERATOR * epsilon + public_key).to_affine() -} - -#[derive(Debug)] -pub struct MultichainSignature { - pub big_r: AffinePoint, - pub s: Scalar, - pub recovery_id: u8, -} +// Constant prefix that ensures delta derivation values are used specifically for +// near-mpc-recovery with key derivation protocol vX.Y.Z. +const DELTA_DERIVATION_PREFIX: &str = "near-mpc-recovery v0.1.0 delta derivation:"; // try to get the correct recovery id for this signature by brute force. pub fn into_eth_sig( public_key: &k256::AffinePoint, - sig: &FullSignature, + big_r: &k256::AffinePoint, + s: &k256::Scalar, msg_hash: Scalar, -) -> anyhow::Result { +) -> anyhow::Result { let public_key = public_key.to_encoded_point(false); - let signature = - k256::ecdsa::Signature::from_scalars(x_coordinate::(&sig.big_r), sig.s) - .context("cannot create signature from cait_sith signature")?; + let signature = k256::ecdsa::Signature::from_scalars(x_coordinate(big_r), s) + .context("cannot create signature from cait_sith signature")?; let pk0 = VerifyingKey::recover_from_prehash( &msg_hash.to_bytes(), &signature, @@ -72,11 +42,7 @@ pub fn into_eth_sig( .context("unable to use 0 as recovery_id to recover public key")? .to_encoded_point(false); if public_key == pk0 { - return Ok(MultichainSignature { - big_r: sig.big_r, - s: sig.s, - recovery_id: 0, - }); + return Ok(SignatureResponse::new(*big_r, *s, 0)); } let pk1 = VerifyingKey::recover_from_prehash( @@ -87,17 +53,8 @@ pub fn into_eth_sig( .context("unable to use 1 as recovery_id to recover public key")? .to_encoded_point(false); if public_key == pk1 { - return Ok(MultichainSignature { - big_r: sig.big_r, - s: sig.s, - recovery_id: 1, - }); + return Ok(SignatureResponse::new(*big_r, *s, 1)); } anyhow::bail!("cannot use either recovery id (0 or 1) to recover pubic key") } - -/// Get the x coordinate of a point, as a scalar -pub fn x_coordinate(point: &C::AffinePoint) -> C::Scalar { - ::Uint>>::reduce_bytes(&point.x()) -} diff --git a/chain-signatures/node/src/protocol/contract/mod.rs b/chain-signatures/node/src/protocol/contract/mod.rs index 860d0a78d..44a7a451d 100644 --- a/chain-signatures/node/src/protocol/contract/mod.rs +++ b/chain-signatures/node/src/protocol/contract/mod.rs @@ -1,7 +1,7 @@ pub mod primitives; -use crate::types::PublicKey; use crate::util::NearPublicKeyExt; +use crypto_shared::PublicKey; use mpc_contract::ProtocolContractState; use near_account_id::AccountId; use serde::{Deserialize, Serialize}; diff --git a/chain-signatures/node/src/protocol/message.rs b/chain-signatures/node/src/protocol/message.rs index 5864a8d57..2328a04b9 100644 --- a/chain-signatures/node/src/protocol/message.rs +++ b/chain-signatures/node/src/protocol/message.rs @@ -4,6 +4,7 @@ use super::state::{GeneratingState, NodeState, ResharingState, RunningState}; use super::triple::TripleId; use crate::gcp::error::SecretStorageError; use crate::http_client::SendError; +use crate::indexer::ContractSignRequest; use crate::mesh::Mesh; use crate::util; @@ -65,7 +66,7 @@ pub struct SignatureMessage { pub receipt_id: CryptoHash, pub proposer: Participant, pub presignature_id: PresignatureId, - pub msg_hash: [u8; 32], + pub request: ContractSignRequest, pub epsilon: Scalar, pub delta: Scalar, pub epoch: u64, @@ -347,7 +348,7 @@ impl MessageHandler for RunningState { *receipt_id, message.proposer, message.presignature_id, - message.msg_hash, + message.request.clone(), message.epsilon, message.delta, &mut presignature_manager, diff --git a/chain-signatures/node/src/protocol/presignature.rs b/chain-signatures/node/src/protocol/presignature.rs index 1f601e239..563d7cf13 100644 --- a/chain-signatures/node/src/protocol/presignature.rs +++ b/chain-signatures/node/src/protocol/presignature.rs @@ -2,12 +2,13 @@ use super::message::PresignatureMessage; use super::triple::{Triple, TripleConfig, TripleId, TripleManager}; use crate::gcp::error::DatastoreStorageError; use crate::protocol::contract::primitives::Participants; -use crate::types::{PresignatureProtocol, PublicKey, SecretKeyShare}; +use crate::types::{PresignatureProtocol, SecretKeyShare}; use crate::util::AffinePointExt; use cait_sith::protocol::{Action, InitializationError, Participant, ProtocolError}; use cait_sith::{KeygenOutput, PresignArguments, PresignOutput}; use chrono::Utc; +use crypto_shared::PublicKey; use k256::Secp256k1; use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet, VecDeque}; diff --git a/chain-signatures/node/src/protocol/signature.rs b/chain-signatures/node/src/protocol/signature.rs index 3e5be85ab..051966ce9 100644 --- a/chain-signatures/node/src/protocol/signature.rs +++ b/chain-signatures/node/src/protocol/signature.rs @@ -1,14 +1,18 @@ use super::contract::primitives::Participants; use super::message::SignatureMessage; use super::presignature::{Presignature, PresignatureId, PresignatureManager}; -use crate::kdf; -use crate::types::{PublicKey, SignatureProtocol}; -use crate::util::{AffinePointExt, ScalarExt}; +use crate::indexer::ContractSignRequest; +use crate::kdf::into_eth_sig; +use crate::types::SignatureProtocol; +use crate::util::AffinePointExt; use cait_sith::protocol::{Action, InitializationError, Participant, ProtocolError}; use cait_sith::{FullSignature, PresignOutput}; use chrono::Utc; +use crypto_shared::{derive_key, PublicKey}; +use crypto_shared::{ScalarExt, SerializableScalar}; use k256::{Scalar, Secp256k1}; +use mpc_contract::SignatureRequest; use rand::rngs::StdRng; use rand::seq::{IteratorRandom, SliceRandom}; use rand::SeedableRng; @@ -25,7 +29,7 @@ pub const COMPLETION_EXISTENCE_TIMEOUT: Duration = Duration::from_secs(120 * 60) pub struct SignRequest { pub receipt_id: CryptoHash, - pub msg_hash: [u8; 32], + pub request: ContractSignRequest, pub epsilon: Scalar, pub delta: Scalar, pub entropy: [u8; 32], @@ -54,7 +58,7 @@ impl SignQueue { pub fn add(&mut self, request: SignRequest) { tracing::info!( receipt_id = %request.receipt_id, - payload = hex::encode(request.msg_hash), + payload = hex::encode(request.request.payload), entropy = hex::encode(request.entropy), "new sign request" ); @@ -115,7 +119,7 @@ pub struct SignatureGenerator { pub participants: Vec, pub proposer: Participant, pub presignature_id: PresignatureId, - pub msg_hash: [u8; 32], + pub request: ContractSignRequest, pub epsilon: Scalar, pub delta: Scalar, pub sign_request_timestamp: Instant, @@ -129,7 +133,7 @@ impl SignatureGenerator { participants: Vec, proposer: Participant, presignature_id: PresignatureId, - msg_hash: [u8; 32], + request: ContractSignRequest, epsilon: Scalar, delta: Scalar, sign_request_timestamp: Instant, @@ -139,7 +143,7 @@ impl SignatureGenerator { participants, proposer, presignature_id, - msg_hash, + request, epsilon, delta, sign_request_timestamp, @@ -163,7 +167,7 @@ impl SignatureGenerator { /// for starting up this failed signature once again. pub struct GenerationRequest { pub proposer: Participant, - pub msg_hash: [u8; 32], + pub request: ContractSignRequest, pub epsilon: Scalar, pub delta: Scalar, pub sign_request_timestamp: Instant, @@ -178,7 +182,12 @@ pub struct SignatureManager { completed: HashMap, /// Generated signatures assigned to the current node that are yet to be published. /// Vec<(receipt_id, msg_hash, timestamp, output)> - signatures: Vec<(CryptoHash, [u8; 32], Instant, FullSignature)>, + signatures: Vec<( + CryptoHash, + SignatureRequest, + Instant, + FullSignature, + )>, me: Participant, public_key: PublicKey, epoch: u64, @@ -216,7 +225,7 @@ impl SignatureManager { let participants = participants.keys_vec(); let GenerationRequest { proposer, - msg_hash, + request, epsilon, delta, sign_request_timestamp, @@ -231,16 +240,16 @@ impl SignatureManager { let protocol = Box::new(cait_sith::sign( &participants, me, - kdf::derive_key(public_key, epsilon), + derive_key(public_key, epsilon), output, - Scalar::from_bytes(&msg_hash), + Scalar::from_bytes(&request.payload), )?); Ok(SignatureGenerator::new( protocol, participants, proposer, presignature.id, - msg_hash, + request, epsilon, delta, sign_request_timestamp, @@ -268,7 +277,7 @@ impl SignatureManager { participants: &Participants, receipt_id: CryptoHash, presignature: Presignature, - msg_hash: [u8; 32], + request: ContractSignRequest, epsilon: Scalar, delta: Scalar, sign_request_timestamp: Instant, @@ -287,7 +296,7 @@ impl SignatureManager { presignature, GenerationRequest { proposer: self.me, - msg_hash, + request, epsilon, delta, sign_request_timestamp, @@ -310,7 +319,7 @@ impl SignatureManager { receipt_id: CryptoHash, proposer: Participant, presignature_id: PresignatureId, - msg_hash: [u8; 32], + request: ContractSignRequest, epsilon: Scalar, delta: Scalar, presignature_manager: &mut PresignatureManager, @@ -330,7 +339,7 @@ impl SignatureManager { presignature, GenerationRequest { proposer, - msg_hash, + request, epsilon, delta, sign_request_timestamp: Instant::now(), @@ -362,7 +371,7 @@ impl SignatureManager { *receipt_id, GenerationRequest { proposer: generator.proposer, - msg_hash: generator.msg_hash, + request: generator.request.clone(), epsilon: generator.epsilon, delta: generator.delta, sign_request_timestamp: generator.sign_request_timestamp @@ -386,7 +395,7 @@ impl SignatureManager { receipt_id: *receipt_id, proposer: generator.proposer, presignature_id: generator.presignature_id, - msg_hash: generator.msg_hash, + request: generator.request.clone(), epsilon: generator.epsilon, delta: generator.delta, epoch: self.epoch, @@ -403,7 +412,7 @@ impl SignatureManager { receipt_id: *receipt_id, proposer: generator.proposer, presignature_id: generator.presignature_id, - msg_hash: generator.msg_hash, + request: generator.request.clone(), epsilon: generator.epsilon, delta: generator.delta, epoch: self.epoch, @@ -422,9 +431,13 @@ impl SignatureManager { "completed signature generation" ); self.completed.insert(generator.presignature_id, Instant::now()); + let request = SignatureRequest { + epsilon: SerializableScalar {scalar: generator.epsilon}, + payload_hash: generator.request.payload, + }; if generator.proposer == self.me { self.signatures - .push((*receipt_id, generator.msg_hash, generator.sign_request_timestamp, output)); + .push((*receipt_id, request, generator.sign_request_timestamp, output)); } // Do not retain the protocol return false; @@ -491,7 +504,7 @@ impl SignatureManager { &sig_participants, receipt_id, presignature, - my_request.msg_hash, + my_request.request, my_request.epsilon, my_request.delta, my_request.time_added, @@ -514,20 +527,21 @@ impl SignatureManager { mpc_contract_id: &AccountId, my_account_id: &AccountId, ) -> Result<(), near_fetch::Error> { - for (receipt_id, payload, time_added, signature) in self.signatures.drain(..) { - // TODO: Figure out how to properly serialize the signature - // let r_s = signature.big_r.x().concat(signature.s.to_bytes()); - // let tag = - // ConditionallySelectable::conditional_select(&2u8, &3u8, signature.big_r.y_is_odd()); - // let signature = r_s.append(tag); - // let signature = Secp256K1Signature::try_from(signature.as_slice()).unwrap(); - // let signature = Signature::SECP256K1(signature); + for (receipt_id, request, time_added, signature) in self.signatures.drain(..) { + let expected_public_key = derive_key(self.public_key, request.epsilon.scalar); + // We do this here, rather than on the client side, so we can use the ecrecover system function on NEAR to validate our signature + let signature = into_eth_sig( + &expected_public_key, + &signature.big_r, + &signature.s, + Scalar::from_bytes(&request.payload_hash), + ) + .map_err(|_| near_fetch::Error::InvalidArgs("Failed to generate a recovery ID"))?; let response = rpc_client .call(signer, mpc_contract_id, "respond") .args_json(serde_json::json!({ - "payload": payload, - "big_r": signature.big_r, - "s": signature.s + "request": request, + "response": signature, })) .max_gas() .transact() @@ -543,7 +557,7 @@ impl SignatureManager { .with_label_values(&[my_account_id.as_str()]) .inc(); } - tracing::info!(%receipt_id, big_r = signature.big_r.to_base58(), s = ?signature.s, status = ?response.status(), "published signature response"); + tracing::info!(%receipt_id, big_r = signature.big_r.affine_point.to_base58(), s = ?signature.s, status = ?response.status(), "published signature response"); } Ok(()) } diff --git a/chain-signatures/node/src/protocol/state.rs b/chain-signatures/node/src/protocol/state.rs index e4a56d110..13f09cf01 100644 --- a/chain-signatures/node/src/protocol/state.rs +++ b/chain-signatures/node/src/protocol/state.rs @@ -6,8 +6,9 @@ use super::triple::TripleManager; use super::SignQueue; use crate::http_client::MessageQueue; use crate::storage::triple_storage::TripleData; -use crate::types::{KeygenProtocol, PublicKey, ReshareProtocol, SecretKeyShare}; +use crate::types::{KeygenProtocol, ReshareProtocol, SecretKeyShare}; use cait_sith::protocol::Participant; +use crypto_shared::PublicKey; use near_account_id::AccountId; use serde::{Deserialize, Serialize}; use std::fmt; diff --git a/chain-signatures/node/src/types.rs b/chain-signatures/node/src/types.rs index 4f36f728d..177650d17 100644 --- a/chain-signatures/node/src/types.rs +++ b/chain-signatures/node/src/types.rs @@ -5,6 +5,7 @@ use cait_sith::protocol::{InitializationError, Participant}; use cait_sith::triples::TripleGenerationOutput; use cait_sith::{protocol::Protocol, KeygenOutput}; use cait_sith::{FullSignature, PresignOutput}; +use crypto_shared::PublicKey; use k256::{elliptic_curve::CurveArithmetic, Secp256k1}; use tokio::sync::{RwLock, RwLockWriteGuard}; @@ -31,7 +32,6 @@ pub const FAILED_TRIPLES_TIMEOUT: Duration = Duration::from_secs(120 * 60); pub const TAKEN_TIMEOUT: Duration = Duration::from_secs(120 * 60); pub type SecretKeyShare = ::Scalar; -pub type PublicKey = ::AffinePoint; pub type TripleProtocol = Box> + Send + Sync>; pub type PresignatureProtocol = Box> + Send + Sync>; diff --git a/chain-signatures/node/src/util.rs b/chain-signatures/node/src/util.rs index bfbf27ee2..8aceb5b2e 100644 --- a/chain-signatures/node/src/util.rs +++ b/chain-signatures/node/src/util.rs @@ -1,8 +1,7 @@ -use crate::types::PublicKey; use chrono::{DateTime, LocalResult, TimeZone, Utc}; -use k256::elliptic_curve::scalar::FromUintUnchecked; +use crypto_shared::{near_public_key_to_affine_point, PublicKey}; use k256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint}; -use k256::{AffinePoint, EncodedPoint, Scalar, U256}; +use k256::{AffinePoint, EncodedPoint}; use std::env; use std::time::Duration; @@ -19,10 +18,7 @@ impl NearPublicKeyExt for String { impl NearPublicKeyExt for near_sdk::PublicKey { fn into_affine_point(self) -> PublicKey { - let mut bytes = self.into_bytes(); - bytes[0] = 0x04; - let point = EncodedPoint::from_bytes(bytes).unwrap(); - PublicKey::from_encoded_point(&point).unwrap() + near_public_key_to_affine_point(self) } } @@ -68,16 +64,6 @@ impl AffinePointExt for AffinePoint { } } -pub trait ScalarExt { - fn from_bytes(bytes: &[u8]) -> Self; -} - -impl ScalarExt for Scalar { - fn from_bytes(bytes: &[u8]) -> Self { - Scalar::from_uint_unchecked(U256::from_le_slice(bytes)) - } -} - pub fn get_triple_timeout() -> Duration { env::var("MPC_RECOVERY_TRIPLE_TIMEOUT_SEC") .map(|val| val.parse::().ok().map(Duration::from_secs)) diff --git a/crypto-shared/Cargo.lock b/crypto-shared/Cargo.lock new file mode 100644 index 000000000..f52bc56f3 --- /dev/null +++ b/crypto-shared/Cargo.lock @@ -0,0 +1,776 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe5b10e214954177fb1dc9fbd20a1a2608fe99e6c832033bdc7cea287a20d77" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a8646f94ab393e43e8b35a2558b1624bed28b97ee09c5d15456e3c9463f46d" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "syn_derive", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-shared" +version = "0.0.0" +dependencies = [ + "anyhow", + "borsh", + "getrandom", + "k256", + "near-account-id", + "near-sdk", + "serde", + "serde_json", +] + +[[package]] +name = "darling" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "serdect", + "signature", + "spki", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if 1.0.0", + "ecdsa", + "elliptic-curve", + "once_cell", + "serdect", + "sha2", + "signature", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "near-account-id" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35cbb989542587b47205e608324ddd391f0cee1c22b4b64ae49f458334b95907" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "near-gas" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e75c875026229902d065e4435804497337b631ec69ba746b102954273e9ad1" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "near-sdk" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520234cfdf04a805ac2f04715889d096eb83fdd5b99ca7d0f8027ae473f891a8" +dependencies = [ + "base64", + "borsh", + "bs58", + "near-account-id", + "near-gas", + "near-sdk-macros", + "near-sys", + "near-token", + "once_cell", + "serde", + "serde_json", + "wee_alloc", +] + +[[package]] +name = "near-sdk-macros" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2fe3fc30068c5f20e89b0985d6104c5cc1c6742dbc6efbf352be4189b9bbf7" +dependencies = [ + "Inflector", + "darling", + "proc-macro2", + "quote", + "serde", + "serde_json", + "strum", + "strum_macros", + "syn", +] + +[[package]] +name = "near-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397688591acf8d3ebf2c2485ba32d4b24fc10aad5334e3ad8ec0b7179bfdf06b" + +[[package]] +name = "near-token" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b68f3f8a2409f72b43efdbeff8e820b81e70824c49fee8572979d789d1683fb" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" + +[[package]] +name = "strum_macros" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/crypto-shared/Cargo.toml b/crypto-shared/Cargo.toml new file mode 100644 index 000000000..3c5ce986c --- /dev/null +++ b/crypto-shared/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "crypto-shared" +edition = "2021" + +[dependencies] +k256 = { version = "0.13.1", features = ["sha256", "ecdsa", "serde", "arithmetic", "expose-field"] } +anyhow = "1" +serde = "1" +borsh = "1.3.0" +near-account-id = "1" +serde_json = "1" +near-sdk = {version = "=5.1.0", features = ["unstable"]} + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { version = "0.2.12", features = ["custom"] } + +[dev-dependencies] diff --git a/crypto-shared/src/kdf.rs b/crypto-shared/src/kdf.rs new file mode 100644 index 000000000..0df5adefc --- /dev/null +++ b/crypto-shared/src/kdf.rs @@ -0,0 +1,89 @@ +use crate::types::{PublicKey, ScalarExt}; +use anyhow::Context; +use k256::{ + ecdsa::{RecoveryId, Signature, VerifyingKey}, + elliptic_curve::{point::AffineCoordinates, sec1::ToEncodedPoint, CurveArithmetic}, + sha2::{Digest, Sha256}, + Scalar, Secp256k1, +}; +use near_account_id::AccountId; + +// Constant prefix that ensures epsilon derivation values are used specifically for +// near-mpc-recovery with key derivation protocol vX.Y.Z. +const EPSILON_DERIVATION_PREFIX: &str = "near-mpc-recovery v0.1.0 epsilon derivation:"; + +pub fn derive_epsilon(predecessor_id: &AccountId, path: &str) -> Scalar { + // TODO: Use a key derivation library instead of doing this manually. + // https://crates.io/crates/hkdf might be a good option? + // + // ',' is ACCOUNT_DATA_SEPARATOR from nearcore that indicate the end + // of the accound id in the trie key. We reuse the same constant to + // indicate the end of the account id in derivation path. + let derivation_path = format!("{EPSILON_DERIVATION_PREFIX}{},{}", predecessor_id, path); + let mut hasher = Sha256::new(); + hasher.update(derivation_path); + let mut bytes = hasher.finalize(); + // Due to a previous bug in our Scalar conversion code, this hash was reversed, we reverse it here to preserve compatibility, but will likely change this later. + bytes.reverse(); + Scalar::from_bytes(&bytes) +} + +pub fn derive_key(public_key: PublicKey, epsilon: Scalar) -> PublicKey { + (::ProjectivePoint::GENERATOR * epsilon + public_key).to_affine() +} + +/// Get the x coordinate of a point, as a scalar +pub fn x_coordinate( + point: &::AffinePoint, +) -> ::Scalar { + <::Scalar as k256::elliptic_curve::ops::Reduce< + ::Uint, + >>::reduce_bytes(&point.x()) +} + +pub fn check_ec_signature( + expected_pk: &k256::AffinePoint, + big_r: &k256::AffinePoint, + s: &k256::Scalar, + msg_hash: Scalar, + recovery_id: u8, +) -> anyhow::Result<()> { + let public_key = expected_pk.to_encoded_point(false); + let signature = k256::ecdsa::Signature::from_scalars(x_coordinate(big_r), s) + .context("cannot create signature from cait_sith signature")?; + let found_pk = recover( + &msg_hash.to_bytes(), + &signature, + RecoveryId::try_from(recovery_id).context("invalid recovery ID")?, + )? + .to_encoded_point(false); + if public_key == found_pk { + return Ok(()); + } + + anyhow::bail!("cannot use either recovery id (0 or 1) to recover pubic key") +} + +// #[cfg(not(target_arch = "wasm32"))] +fn recover( + prehash: &[u8], + signature: &Signature, + recovery_id: RecoveryId, +) -> anyhow::Result { + VerifyingKey::recover_from_prehash(prehash, signature, recovery_id) + .context("Unable to recover public key") +} + +// TODO Migrate to native function +// #[cfg(target_arch = "wasm32")] +// fn recover( +// prehash: &[u8], +// signature: &Signature, +// recovery_id: RecoveryId, +// ) -> anyhow::Result { +// use near_sdk::env; +// let recovered_key = +// env::ecrecover(prehash, &signature.to_bytes(), recovery_id.to_byte(), false) +// .context("Unable to recover public key")?; +// VerifyingKey::try_from(&recovered_key[..]).context("Failed to parse returned key") +// } diff --git a/crypto-shared/src/lib.rs b/crypto-shared/src/lib.rs new file mode 100644 index 000000000..763cba839 --- /dev/null +++ b/crypto-shared/src/lib.rs @@ -0,0 +1,28 @@ +pub mod kdf; +pub mod types; + +use k256::elliptic_curve::sec1::FromEncodedPoint; +use k256::EncodedPoint; +pub use kdf::{derive_epsilon, derive_key, x_coordinate}; +pub use types::{ + PublicKey, ScalarExt, SerializableAffinePoint, SerializableScalar, SignatureResponse, +}; + +// Our wasm runtime doesn't support good syncronous entropy. +// We could use something VRF + pseudorandom here, but someone would likely shoot themselves in the foot with it. +// Our crypto libraries should definately panic, because they normally expect randomness to be private +#[cfg(target_arch = "wasm32")] +use getrandom::{register_custom_getrandom, Error}; +#[cfg(target_arch = "wasm32")] +pub fn randomness_unsupported(_: &mut [u8]) -> Result<(), Error> { + Err(Error::UNSUPPORTED) +} +#[cfg(target_arch = "wasm32")] +register_custom_getrandom!(randomness_unsupported); + +pub fn near_public_key_to_affine_point(pk: near_sdk::PublicKey) -> PublicKey { + let mut bytes = pk.into_bytes(); + bytes[0] = 0x04; + let point = EncodedPoint::from_bytes(bytes).unwrap(); + PublicKey::from_encoded_point(&point).unwrap() +} diff --git a/crypto-shared/src/types.rs b/crypto-shared/src/types.rs new file mode 100644 index 000000000..639a83767 --- /dev/null +++ b/crypto-shared/src/types.rs @@ -0,0 +1,109 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use k256::{ + elliptic_curve::{scalar::FromUintUnchecked, CurveArithmetic}, + AffinePoint, Scalar, Secp256k1, U256, +}; +use serde::{Deserialize, Serialize}; + +pub type PublicKey = ::AffinePoint; + +pub trait ScalarExt { + fn from_bytes(bytes: &[u8]) -> Self; +} + +// TODO prevent bad scalars from beind sent +impl ScalarExt for Scalar { + fn from_bytes(bytes: &[u8]) -> Self { + Scalar::from_uint_unchecked(U256::from_be_slice(bytes)) + } +} + +// Is there a better way to force a borsh serialization? +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)] +pub struct SerializableScalar { + pub scalar: Scalar, +} + +impl BorshSerialize for SerializableScalar { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + let to_ser: [u8; 32] = self.scalar.to_bytes().into(); + BorshSerialize::serialize(&to_ser, writer) + } +} + +impl BorshDeserialize for SerializableScalar { + fn deserialize_reader(reader: &mut R) -> std::io::Result { + let from_ser: [u8; 32] = BorshDeserialize::deserialize_reader(reader)?; + let scalar = Scalar::from_bytes(&from_ser[..]); + Ok(SerializableScalar { scalar }) + } +} + +// Is there a better way to force a borsh serialization? +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)] +pub struct SerializableAffinePoint { + pub affine_point: AffinePoint, +} + +impl BorshSerialize for SerializableAffinePoint { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + let to_ser: Vec = serde_json::to_vec(&self.affine_point)?; + BorshSerialize::serialize(&to_ser, writer) + } +} + +impl BorshDeserialize for SerializableAffinePoint { + fn deserialize_reader(reader: &mut R) -> std::io::Result { + let from_ser: Vec = BorshDeserialize::deserialize_reader(reader)?; + let affine_point = serde_json::from_slice(&from_ser)?; + Ok(SerializableAffinePoint { affine_point }) + } +} + +#[test] +fn serializeable_scalar_roundtrip() { + use k256::elliptic_curve::PrimeField; + let test_vec = vec![ + Scalar::ZERO, + Scalar::ONE, + Scalar::from_u128(u128::MAX), + Scalar::from_bytes(&[3; 32]), + ]; + + for scalar in test_vec.into_iter() { + let input = SerializableScalar { scalar }; + // Test borsh + { + let serialized = borsh::to_vec(&input).unwrap(); + let output: SerializableScalar = borsh::from_slice(&serialized).unwrap(); + assert_eq!(input, output, "Failed on {:?}", scalar); + } + + dbg!(scalar); + // Test Serde via JSON + { + let serialized = serde_json::to_vec(&input).unwrap(); + let output: SerializableScalar = serde_json::from_slice(&serialized).unwrap(); + assert_eq!(input, output, "Failed on {:?}", scalar); + } + } +} + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone)] +pub struct SignatureResponse { + pub big_r: SerializableAffinePoint, + pub s: SerializableScalar, + pub recovery_id: u8, +} + +impl SignatureResponse { + pub fn new(big_r: AffinePoint, s: Scalar, recovery_id: u8) -> Self { + SignatureResponse { + big_r: SerializableAffinePoint { + affine_point: big_r, + }, + s: SerializableScalar { scalar: s }, + recovery_id, + } + } +} diff --git a/integration-tests/chain-signatures/Cargo.lock b/integration-tests/chain-signatures/Cargo.lock index b2ba33e32..66809537c 100644 --- a/integration-tests/chain-signatures/Cargo.lock +++ b/integration-tests/chain-signatures/Cargo.lock @@ -2084,6 +2084,20 @@ dependencies = [ "subtle", ] +[[package]] +name = "crypto-shared" +version = "0.0.0" +dependencies = [ + "anyhow", + "borsh 1.5.0", + "getrandom 0.2.15", + "k256", + "near-account-id", + "near-sdk", + "serde", + "serde_json", +] + [[package]] name = "csv" version = "1.3.0" @@ -3964,6 +3978,7 @@ dependencies = [ "backon", "bollard", "cait-sith", + "crypto-shared", "ecdsa 0.16.9", "elliptic-curve 0.13.8", "ethers-core", @@ -4548,6 +4563,8 @@ name = "mpc-contract" version = "0.2.0" dependencies = [ "borsh 1.5.0", + "crypto-shared", + "k256", "near-sdk", "schemars", "serde", @@ -4578,6 +4595,7 @@ dependencies = [ "cait-sith", "chrono", "clap", + "crypto-shared", "google-datastore1", "google-secretmanager1", "hex 0.4.3", diff --git a/integration-tests/chain-signatures/Cargo.toml b/integration-tests/chain-signatures/Cargo.toml index 4d1ee3eaf..9c5936589 100644 --- a/integration-tests/chain-signatures/Cargo.toml +++ b/integration-tests/chain-signatures/Cargo.toml @@ -22,6 +22,7 @@ testcontainers = { version = "0.15", features = ["experimental"] } tokio = { version = "1.28", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } +crypto-shared = { path = "../../crypto-shared" } # crypto dependencies cait-sith = { git = "https://github.com/LIT-Protocol/cait-sith.git", features = [ diff --git a/integration-tests/chain-signatures/tests/actions/mod.rs b/integration-tests/chain-signatures/tests/actions/mod.rs index c7ea89ee5..f7e56c023 100644 --- a/integration-tests/chain-signatures/tests/actions/mod.rs +++ b/integration-tests/chain-signatures/tests/actions/mod.rs @@ -3,17 +3,20 @@ pub mod wait_for; use crate::MultichainTestContext; use cait_sith::FullSignature; +use crypto_shared::ScalarExt; +use crypto_shared::SerializableAffinePoint; +use crypto_shared::{derive_epsilon, derive_key, SerializableScalar, SignatureResponse}; use elliptic_curve::sec1::ToEncodedPoint; use k256::ecdsa::VerifyingKey; use k256::elliptic_curve::ops::{Invert, Reduce}; use k256::elliptic_curve::point::AffineCoordinates; -use k256::elliptic_curve::scalar::FromUintUnchecked; use k256::elliptic_curve::sec1::FromEncodedPoint; use k256::elliptic_curve::ProjectivePoint; use k256::{AffinePoint, EncodedPoint, Scalar, Secp256k1}; +use mpc_contract::primitives::SignRequest; use mpc_contract::RunningContractState; -use mpc_recovery_node::kdf; -use mpc_recovery_node::util::ScalarExt; +use mpc_contract::SignatureRequest; +use mpc_recovery_node::kdf::into_eth_sig; use near_crypto::InMemorySigner; use near_jsonrpc_client::methods::broadcast_tx_async::RpcBroadcastTxAsyncRequest; use near_lake_primitives::CryptoHash; @@ -48,6 +51,12 @@ pub async fn request_sign( .rpc_client .fetch_nonce(&signer.account_id, &signer.public_key) .await?; + + let request = SignRequest { + payload: payload_hashed, + path: "test".to_string(), + key_version: 0, + }; let tx_hash = ctx .jsonrpc_client .call(&RpcBroadcastTxAsyncRequest { @@ -60,9 +69,7 @@ pub async fn request_sign( actions: vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "sign".to_string(), args: serde_json::to_vec(&serde_json::json!({ - "payload": payload_hashed, - "path": "test", - "key_version": 0, + "request": request, }))?, gas: 300_000_000_000_000, deposit: 0, @@ -83,12 +90,39 @@ pub async fn assert_signature( ) { let mpc_point = EncodedPoint::from_bytes(mpc_pk_bytes).unwrap(); let mpc_pk = AffinePoint::from_encoded_point(&mpc_point).unwrap(); - let epsilon = kdf::derive_epsilon(account_id, "test"); - let user_pk = kdf::derive_key(mpc_pk, epsilon); + let epsilon = derive_epsilon(account_id, "test"); + let user_pk = derive_key(mpc_pk, epsilon); assert!(signature.verify(&user_pk, &Scalar::from_bytes(payload),)); } +// A normal signature, but we try to insert a bad response which fails and the signature is generated +pub async fn single_signature_rogue_responder( + ctx: &MultichainTestContext<'_>, + state: &RunningContractState, +) -> anyhow::Result<()> { + let (_, payload_hash, account, tx_hash) = request_sign(ctx).await?; + + // We have to use seperate transactions because one could fail. + // This leads to a potential race condition where this transaction could get sent after the signature completes, but I think that's unlikely + let rogue_hash = rogue_respond(ctx, payload_hash, account.id(), "test").await?; + + let err = wait_for::rogue_message_responded(ctx, rogue_hash).await?; + + assert_eq!( + err, + "Smart contract panicked: Signature could not be verified".to_string() + ); + + let signature = wait_for::signature_responded(ctx, tx_hash).await?; + + let mut mpc_pk_bytes = vec![0x04]; + mpc_pk_bytes.extend_from_slice(&state.public_key.as_bytes()[1..]); + assert_signature(account.id(), &mpc_pk_bytes, &payload_hash, &signature).await; + + Ok(()) +} + pub async fn single_signature_production( ctx: &MultichainTestContext<'_>, state: &RunningContractState, @@ -103,6 +137,74 @@ pub async fn single_signature_production( Ok(()) } +pub async fn rogue_respond( + ctx: &MultichainTestContext<'_>, + payload_hash: [u8; 32], + predecessor: &near_workspaces::AccountId, + path: &str, +) -> anyhow::Result { + let worker = &ctx.nodes.ctx().worker; + let account = worker.dev_create_account().await?; + + let signer = InMemorySigner { + account_id: account.id().clone(), + public_key: account.secret_key().public_key().clone().into(), + secret_key: account.secret_key().to_string().parse()?, + }; + let (nonce, block_hash, _) = ctx + .rpc_client + .fetch_nonce(&signer.account_id, &signer.public_key) + .await?; + let epsilon = derive_epsilon(predecessor, path); + + let request = SignatureRequest { + payload_hash, + epsilon: SerializableScalar { scalar: epsilon }, + }; + + let big_r = serde_json::from_value( + "02EC7FA686BB430A4B700BDA07F2E07D6333D9E33AEEF270334EB2D00D0A6FEC6C".into(), + )?; // Fake BigR + let s = serde_json::from_value( + "20F90C540EE00133C911EA2A9ADE2ABBCC7AD820687F75E011DFEEC94DB10CD6".into(), + )?; // Fake S + + let response = SignatureResponse { + big_r: SerializableAffinePoint { + affine_point: big_r, + }, + s: SerializableScalar { scalar: s }, + recovery_id: 0, + }; + + let json = &serde_json::json!({ + "request": request, + "response": response, + }); + let hash = ctx + .jsonrpc_client + .call(&RpcBroadcastTxAsyncRequest { + signed_transaction: Transaction { + nonce, + block_hash, + signer_id: signer.account_id.clone(), + public_key: signer.public_key.clone(), + receiver_id: ctx.nodes.ctx().mpc_contract.id().clone(), + actions: vec![Action::FunctionCall(Box::new(FunctionCallAction { + method_name: "respond".to_string(), + args: serde_json::to_vec(json)?, + gas: 300_000_000_000_000, + deposit: 0, + }))], + } + .sign(&signer), + }) + .await?; + + tokio::time::sleep(Duration::from_secs(1)).await; + Ok(hash) +} + pub async fn request_sign_non_random( ctx: &MultichainTestContext<'_>, account: Account, @@ -118,6 +220,13 @@ pub async fn request_sign_non_random( .rpc_client .fetch_nonce(&signer.account_id, &signer.public_key) .await?; + + let request = SignRequest { + payload: payload_hashed, + path: "test".to_string(), + key_version: 0, + }; + let tx_hash = ctx .jsonrpc_client .call(&RpcBroadcastTxAsyncRequest { @@ -130,9 +239,7 @@ pub async fn request_sign_non_random( actions: vec![Action::FunctionCall(Box::new(FunctionCallAction { method_name: "sign".to_string(), args: serde_json::to_vec(&serde_json::json!({ - "payload": payload_hashed, - "path": "test", - "key_version": 0, + "request": request, }))?, gas: 300_000_000_000_000, deposit: 0, @@ -166,38 +273,42 @@ pub async fn single_payload_signature_production( Ok(()) } +// This code was and still is a bit of a mess. +// Previously converting a Scalar to bytes reversed the bytes and converted to a Scalar. +// The big_r and s values were generated using chain signatures from an older commit, therefore the signature is generated against a reversed hash. +// This shows that the old signatures will verify against a reversed payload #[tokio::test] -async fn test_proposition() { +async fn test_old_signatures_verify() { + use k256::sha2::{Digest, Sha256}; let big_r = "044bf886afee5a6844a25fa6831a01715e990d3d9e96b792a9da91cfbecbf8477cea57097a3db9fc1d4822afade3d1c4e6d66e99568147304ae34bcfa609d90a16"; let s = "1f871c67139f617409067ac8a7150481e3a5e2d8a9207ffdaad82098654e95cb"; let mpc_key = "02F2B55346FD5E4BFF1F06522561BDCD024CEA25D98A091197ACC04E22B3004DB2"; + let account_id = "acc_mc.test.near"; - // Create payload let mut payload = [0u8; 32]; for (i, item) in payload.iter_mut().enumerate() { *item = i as u8; } - // TODO: get hashed values on the flight - let payload_hash: [u8; 32] = [ - 99, 13, 205, 41, 102, 196, 51, 102, 145, 18, 84, 72, 187, 178, 91, 79, 244, 18, 164, 156, - 115, 45, 178, 200, 171, 193, 184, 88, 27, 215, 16, 221, - ]; - let payload_hash_scalar = k256::Scalar::from_bytes(&payload_hash); // TODO: why do we need both reversed and not reversed versions? - let mut payload_hash_reversed: [u8; 32] = payload_hash; - payload_hash_reversed.reverse(); + let mut hasher = Sha256::new(); + hasher.update(payload); + + let mut payload_hash: [u8; 32] = hasher.finalize().into(); + payload_hash.reverse(); + + let payload_hash_scalar = Scalar::from_bytes(&payload_hash); println!("payload_hash: {payload_hash:?}"); println!("payload_hash_scallar: {payload_hash_scalar:#?}"); - println!("payload_hash_reversed: {payload_hash_reversed:?}"); // Derive and convert user pk let mpc_pk = hex::decode(mpc_key).unwrap(); let mpc_pk = EncodedPoint::from_bytes(mpc_pk).unwrap(); let mpc_pk = AffinePoint::from_encoded_point(&mpc_pk).unwrap(); - let account_id = "acc_mc.test.near".parse().unwrap(); - let derivation_epsilon: k256::Scalar = kdf::derive_epsilon(&account_id, "test"); - let user_pk: AffinePoint = kdf::derive_key(mpc_pk, derivation_epsilon); + + let account_id = account_id.parse().unwrap(); + let derivation_epsilon: k256::Scalar = derive_epsilon(&account_id, "test"); + let user_pk: AffinePoint = derive_key(mpc_pk, derivation_epsilon); let user_pk_y_parity = match user_pk.y_is_odd().unwrap_u8() { 0 => secp256k1::Parity::Even, 1 => secp256k1::Parity::Odd, @@ -217,22 +328,30 @@ async fn test_proposition() { assert!(big_r_y_parity == 0 || big_r_y_parity == 1); let s = hex::decode(s).unwrap(); - let s = k256::Scalar::from_uint_unchecked(k256::U256::from_be_slice(s.as_slice())); + let s = k256::Scalar::from_bytes(s.as_slice()); let r = x_coordinate::(&big_r); let signature = cait_sith::FullSignature:: { big_r, s }; - let multichain_sig = kdf::into_eth_sig(&user_pk, &signature, payload_hash_scalar).unwrap(); - println!("{multichain_sig:#?}"); println!("R: {big_r:#?}"); println!("r: {r:#?}"); println!("y parity: {}", big_r_y_parity); println!("s: {s:#?}"); + println!("epsilon: {derivation_epsilon:#?}"); + + let multichain_sig = into_eth_sig( + &user_pk, + &signature.big_r, + &signature.s, + payload_hash_scalar, + ) + .unwrap(); + println!("{multichain_sig:#?}"); // Check signature using cait-sith tooling let is_signature_valid_for_user_pk = signature.verify(&user_pk, &payload_hash_scalar); let is_signature_valid_for_mpc_pk = signature.verify(&mpc_pk, &payload_hash_scalar); - let another_user_pk = kdf::derive_key(mpc_pk, derivation_epsilon + k256::Scalar::ONE); + let another_user_pk = derive_key(mpc_pk, derivation_epsilon + k256::Scalar::ONE); let is_signature_valid_for_another_user_pk = signature.verify(&another_user_pk, &payload_hash_scalar); assert!(is_signature_valid_for_user_pk); @@ -246,7 +365,7 @@ async fn test_proposition() { let ecdsa_local_verify_result = verify( &k256::ecdsa::VerifyingKey::from(&user_pk_k256), - &payload_hash_reversed, + &payload_hash, &k256_sig, ); assert!(ecdsa_local_verify_result.is_ok()); @@ -279,9 +398,7 @@ async fn test_proposition() { let user_address_ethers: ethers_core::types::H160 = ethers_core::utils::public_key_to_address(&verifying_user_pk); - assert!(signature - .verify(payload_hash_reversed, user_address_ethers) - .is_ok()); + assert!(signature.verify(payload_hash, user_address_ethers).is_ok()); // Check if recovered address is the same as the user address let signature_for_recovery: [u8; 64] = { @@ -292,21 +409,20 @@ async fn test_proposition() { }; let recovered_from_signature_address_web3 = web3::signing::recover( - &payload_hash_reversed, + &payload_hash, &signature_for_recovery, multichain_sig.recovery_id as i32, ) .unwrap(); assert_eq!(user_address_from_pk, recovered_from_signature_address_web3); - let recovered_from_signature_address_ethers = signature.recover(payload_hash_reversed).unwrap(); + let recovered_from_signature_address_ethers = signature.recover(payload_hash).unwrap(); assert_eq!( user_address_from_pk, recovered_from_signature_address_ethers ); - let recovered_from_signature_address_local_function = - recover(signature, payload_hash_reversed).unwrap(); + let recovered_from_signature_address_local_function = recover(signature, payload_hash).unwrap(); assert_eq!( user_address_from_pk, recovered_from_signature_address_local_function diff --git a/integration-tests/chain-signatures/tests/actions/wait_for.rs b/integration-tests/chain-signatures/tests/actions/wait_for.rs index d4e0d3d61..72e2efc74 100644 --- a/integration-tests/chain-signatures/tests/actions/wait_for.rs +++ b/integration-tests/chain-signatures/tests/actions/wait_for.rs @@ -5,8 +5,7 @@ use anyhow::Context; use backon::ExponentialBuilder; use backon::Retryable; use cait_sith::FullSignature; -use k256::AffinePoint; -use k256::Scalar; +use crypto_shared::SignatureResponse; use k256::Secp256k1; use mpc_contract::ProtocolContractState; use mpc_contract::RunningContractState; @@ -14,6 +13,7 @@ use mpc_recovery_node::web::StateView; use near_jsonrpc_client::methods::tx::RpcTransactionStatusRequest; use near_jsonrpc_client::methods::tx::TransactionInfo; use near_lake_primitives::CryptoHash; +use near_primitives::errors::ActionErrorKind; use near_primitives::views::FinalExecutionStatus; use near_workspaces::Account; @@ -224,14 +224,19 @@ pub async fn signature_responded( let Some(outcome) = outcome_view.final_execution_outcome else { anyhow::bail!("final execution outcome not available"); }; + let outcome = outcome.into_outcome(); let FinalExecutionStatus::SuccessValue(payload) = outcome.status else { anyhow::bail!("tx finished unsuccessfully: {:?}", outcome.status); }; - let (big_r, s): (AffinePoint, Scalar) = serde_json::from_slice(&payload)?; - let signature = cait_sith::FullSignature:: { big_r, s }; + let result: SignatureResponse = serde_json::from_slice(&payload)?; + let signature = cait_sith::FullSignature:: { + big_r: result.big_r.affine_point, + s: result.s.scalar, + }; + Ok(signature) }; @@ -260,3 +265,52 @@ pub async fn signature_payload_responded( .with_context(|| "failed to wait for signature response")?; Ok(signature) } + +// Check that the rogue message failed +pub async fn rogue_message_responded( + ctx: &MultichainTestContext<'_>, + tx_hash: CryptoHash, +) -> anyhow::Result { + let is_tx_ready = || async { + let outcome_view = ctx + .jsonrpc_client + .call(RpcTransactionStatusRequest { + transaction_info: TransactionInfo::TransactionId { + tx_hash, + sender_account_id: ctx.nodes.ctx().mpc_contract.id().clone(), + }, + wait_until: near_primitives::views::TxExecutionStatus::Final, + }) + .await?; + + let Some(outcome) = outcome_view.final_execution_outcome else { + anyhow::bail!("final execution outcome not available"); + }; + let outcome = outcome.into_outcome(); + + let FinalExecutionStatus::Failure(ref failure) = outcome.status else { + anyhow::bail!("tx finished successfully: {:?}", outcome.status); + }; + + use near_primitives::errors::TxExecutionError; + let TxExecutionError::ActionError(action_err) = failure else { + anyhow::bail!("invalid transaction: {:?}", outcome.status); + }; + + let ActionErrorKind::FunctionCallError(ref err) = action_err.kind else { + anyhow::bail!("Not a function call error {:?}", outcome.status); + }; + use near_primitives::errors::FunctionCallError; + let FunctionCallError::ExecutionError(err_msg) = err else { + anyhow::bail!("Wrong error type: {:?}", err); + }; + Ok(err_msg.clone()) + }; + + let signature = is_tx_ready + .retry(&ExponentialBuilder::default().with_max_times(6)) + .await + .with_context(|| "failed to wait for rogue message response")?; + + Ok(signature.clone()) +} diff --git a/integration-tests/chain-signatures/tests/cases/mod.rs b/integration-tests/chain-signatures/tests/cases/mod.rs index fe78b4f32..2b03dfecf 100644 --- a/integration-tests/chain-signatures/tests/cases/mod.rs +++ b/integration-tests/chain-signatures/tests/cases/mod.rs @@ -3,15 +3,16 @@ use std::str::FromStr; use crate::actions::{self, wait_for}; use crate::with_multichain_nodes; +use crypto_shared::{self, derive_epsilon, derive_key, x_coordinate, ScalarExt}; use integration_tests_chain_signatures::containers::{self, DockerClient}; use integration_tests_chain_signatures::MultichainConfig; use k256::elliptic_curve::point::AffineCoordinates; -use mpc_recovery_node::kdf::{self, x_coordinate}; +use mpc_recovery_node::kdf::into_eth_sig; use mpc_recovery_node::protocol::presignature::PresignatureConfig; use mpc_recovery_node::protocol::triple::TripleConfig; use mpc_recovery_node::test_utils; use mpc_recovery_node::types::LatestBlockHeight; -use mpc_recovery_node::util::{NearPublicKeyExt, ScalarExt}; +use mpc_recovery_node::util::NearPublicKeyExt; use test_log::test; #[test(tokio::test)] @@ -59,7 +60,7 @@ async fn test_signature_basic() -> anyhow::Result<()> { assert_eq!(state_0.participants.len(), 3); wait_for::has_at_least_triples(&ctx, 2).await?; wait_for::has_at_least_presignatures(&ctx, 2).await?; - actions::single_signature_production(&ctx, &state_0).await + actions::single_signature_rogue_responder(&ctx, &state_0).await }) }) .await @@ -163,22 +164,21 @@ async fn test_key_derivation() -> anyhow::Result<()> { for _ in 0..3 { let mpc_pk: k256::AffinePoint = state_0.public_key.clone().into_affine_point(); let (_, payload_hashed, account, tx_hash) = actions::request_sign(&ctx).await?; - let payload_hashed_rev = { - let mut rev = payload_hashed; - rev.reverse(); - rev - }; let sig = wait_for::signature_responded(&ctx, tx_hash).await?; let hd_path = "test"; - let derivation_epsilon = kdf::derive_epsilon(account.id(), hd_path); - let user_pk = kdf::derive_key(mpc_pk, derivation_epsilon); - let multichain_sig = - kdf::into_eth_sig(&user_pk, &sig, k256::Scalar::from_bytes(&payload_hashed)) - .unwrap(); + let derivation_epsilon = derive_epsilon(account.id(), hd_path); + let user_pk = derive_key(mpc_pk, derivation_epsilon); + let multichain_sig = into_eth_sig( + &user_pk, + &sig.big_r, + &sig.s, + k256::Scalar::from_bytes(&payload_hashed), + ) + .unwrap(); // start recovering the address and compare them: - let user_pk_x = kdf::x_coordinate::(&user_pk); + let user_pk_x = x_coordinate(&user_pk); let user_pk_y_parity = match user_pk.y_is_odd().unwrap_u8() { 1 => secp256k1::Parity::Odd, 0 => secp256k1::Parity::Even, @@ -189,16 +189,16 @@ async fn test_key_derivation() -> anyhow::Result<()> { let user_secp_pk = secp256k1::PublicKey::from_x_only_public_key(user_pk_x, user_pk_y_parity); let user_addr = actions::public_key_to_address(&user_secp_pk); - let r = x_coordinate::(&multichain_sig.big_r); + let r = x_coordinate(&multichain_sig.big_r.affine_point); let s = multichain_sig.s; let signature_for_recovery: [u8; 64] = { let mut signature = [0u8; 64]; signature[..32].copy_from_slice(&r.to_bytes()); - signature[32..].copy_from_slice(&s.to_bytes()); + signature[32..].copy_from_slice(&s.scalar.to_bytes()); signature }; let recovered_addr = web3::signing::recover( - &payload_hashed_rev, + &payload_hashed, &signature_for_recovery, multichain_sig.recovery_id as i32, ) diff --git a/load-tests/Cargo.lock b/load-tests/Cargo.lock index fbabb7f03..c77e3ab26 100644 --- a/load-tests/Cargo.lock +++ b/load-tests/Cargo.lock @@ -816,12 +816,12 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe5b10e214954177fb1dc9fbd20a1a2608fe99e6c832033bdc7cea287a20d77" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" dependencies = [ - "borsh-derive 1.5.0", - "cfg_aliases", + "borsh-derive 1.5.1", + "cfg_aliases 0.2.1", ] [[package]] @@ -852,9 +852,9 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a8646f94ab393e43e8b35a2558b1624bed28b97ee09c5d15456e3c9463f46d" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" dependencies = [ "once_cell", "proc-macro-crate 3.1.0", @@ -1142,6 +1142,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.38" @@ -4047,7 +4053,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c49593c9e94454a2368a4c0a511bf4bf1413aff4d23f16e1d8f4e64b5215351" dependencies = [ - "borsh 1.5.0", + "borsh 1.5.1", "schemars", "semver 1.0.23", "serde", @@ -4109,7 +4115,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35cbb989542587b47205e608324ddd391f0cee1c22b4b64ae49f458334b95907" dependencies = [ - "borsh 1.5.0", + "borsh 1.5.1", "serde", ] @@ -4308,7 +4314,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2991d2912218a80ec0733ac87f84fa803accea105611eea209d4419271957667" dependencies = [ "blake2", - "borsh 1.5.0", + "borsh 1.5.1", "bs58 0.4.0", "c2-chacha", "curve25519-dalek 4.1.2", @@ -4335,7 +4341,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d927e95742aea981b9fd60996fbeba3b61e90acafd54c2c3c2a4ed40065ff03" dependencies = [ "blake2", - "borsh 1.5.0", + "borsh 1.5.1", "bs58 0.4.0", "c2-chacha", "curve25519-dalek 4.1.2", @@ -4427,7 +4433,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14e75c875026229902d065e4435804497337b631ec69ba746b102954273e9ad1" dependencies = [ - "borsh 1.5.0", + "borsh 1.5.1", "interactive-clap", "schemars", "serde", @@ -4458,7 +4464,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18ad81e015f7aced8925d5b9ba3f369b36da9575c15812cfd0786bc1213284ca" dependencies = [ - "borsh 1.5.0", + "borsh 1.5.1", "lazy_static", "log", "near-chain-configs 0.20.1", @@ -4477,7 +4483,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5225c0f97a61fd4534dee3169959dd91bb812be7d0573c1130a3cf86fd16b3e" dependencies = [ - "borsh 1.5.0", + "borsh 1.5.1", "lazy_static", "log", "near-chain-configs 0.21.2", @@ -4627,7 +4633,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9f16a59b6c3e69b0585be951af6fe42a0ba86c0e207cb8c63badd19efd16680" dependencies = [ "assert_matches", - "borsh 1.5.0", + "borsh 1.5.1", "enum-map", "near-account-id 1.0.0", "near-primitives-core 0.20.1", @@ -4646,7 +4652,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a996c8654020f7eb3c11039cb39123fd4cd78654fde4c9e7c3fd6d092c84f342" dependencies = [ "assert_matches", - "borsh 1.5.0", + "borsh 1.5.1", "enum-map", "near-account-id 1.0.0", "near-primitives-core 0.21.2", @@ -4703,7 +4709,7 @@ checksum = "0462b067732132babcc89d5577db3bfcb0a1bcfbaaed3f2db4c11cd033666314" dependencies = [ "arbitrary", "base64 0.21.7", - "borsh 1.5.0", + "borsh 1.5.1", "bytesize", "cfg-if 1.0.0", "chrono", @@ -4745,7 +4751,7 @@ checksum = "7c880397c022d3b8f592cef18f85fd6e79181a2a04c31154afb1730f9fa21098" dependencies = [ "arbitrary", "base64 0.21.7", - "borsh 1.5.0", + "borsh 1.5.1", "bytesize", "cfg-if 1.0.0", "chrono", @@ -4809,7 +4815,7 @@ checksum = "8443eb718606f572c438be6321a097a8ebd69f8e48d953885b4f16601af88225" dependencies = [ "arbitrary", "base64 0.21.7", - "borsh 1.5.0", + "borsh 1.5.1", "bs58 0.4.0", "derive_more", "enum-map", @@ -4831,7 +4837,7 @@ checksum = "082b1d3f6c7e273ec5cd9588e00bdbfc51be6cc9a3a7ec31fc899b4b7d2d3f9d" dependencies = [ "arbitrary", "base64 0.21.7", - "borsh 1.5.0", + "borsh 1.5.1", "bs58 0.4.0", "derive_more", "enum-map", @@ -4968,7 +4974,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b68f3f8a2409f72b43efdbeff8e820b81e70824c49fee8572979d789d1683fb" dependencies = [ - "borsh 1.5.0", + "borsh 1.5.1", "interactive-clap", "serde", ] @@ -5058,7 +5064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c56c80bdb1954808f59bd36a9112377197b38d424991383bf05f52d0fe2e0da5" dependencies = [ "base64 0.21.7", - "borsh 1.5.0", + "borsh 1.5.1", "ed25519-dalek 2.1.1", "enum-map", "memoffset 0.8.0", @@ -5089,7 +5095,7 @@ checksum = "20569500ca56e161c6ed81da9a24c7bf7b974c4238b2f08b2582113b66fa0060" dependencies = [ "anyhow", "base64 0.21.7", - "borsh 1.5.0", + "borsh 1.5.1", "ed25519-dalek 2.1.1", "enum-map", "finite-wasm", @@ -5279,7 +5285,7 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.5.0", "cfg-if 1.0.0", - "cfg_aliases", + "cfg_aliases 0.1.1", "libc", ] diff --git a/load-tests/src/multichain/mod.rs b/load-tests/src/multichain/mod.rs index e8125a4af..e49472b91 100644 --- a/load-tests/src/multichain/mod.rs +++ b/load-tests/src/multichain/mod.rs @@ -10,9 +10,17 @@ use near_primitives::{ }; use rand::Rng; use reqwest::header::CONTENT_TYPE; +use serde::{Deserialize, Serialize}; use crate::fastauth::primitives::UserSession; +#[derive(Serialize, Deserialize, Debug)] +pub struct SignRequest { + pub payload: [u8; 32], + pub path: String, + pub key_version: u32, +} + pub async fn multichain_sign(user: &mut GooseUser) -> TransactionResult { tracing::info!("multichain_sign"); @@ -42,6 +50,12 @@ pub async fn multichain_sign(user: &mut GooseUser) -> TransactionResult { let payload_hashed: [u8; 32] = rand::thread_rng().gen(); tracing::info!("requesting signature for: {:?}", payload_hashed); + let request = SignRequest { + payload: payload_hashed, + path: "test".to_string(), + key_version: 0, + }; + let transaction = Transaction { signer_id: session.near_account_id.clone(), public_key: session.fa_sk.public_key(), @@ -51,9 +65,7 @@ pub async fn multichain_sign(user: &mut GooseUser) -> TransactionResult { actions: vec![Action::FunctionCall(FunctionCallAction { method_name: "sign".to_string(), args: serde_json::to_vec(&serde_json::json!({ - "payload": payload_hashed, - "path": "test", - "key_version": 0, + "request": request, })) .unwrap(), gas: 300_000_000_000_000,