From ec9e2e7ff07d1ee52ba32cbd9cb407e1368eed4a Mon Sep 17 00:00:00 2001 From: kevindeforth Date: Fri, 9 Jan 2026 17:08:10 +0100 Subject: [PATCH 1/7] unify ckd and sign sandbox tests --- crates/contract/tests/sandbox/ckd.rs | 374 --------------- crates/contract/tests/sandbox/mod.rs | 1 - crates/contract/tests/sandbox/sign.rs | 264 +++++++---- .../sandbox/upgrade_to_current_contract.rs | 13 +- .../tests/sandbox/utils/sign_utils.rs | 443 ++++++++++-------- 5 files changed, 421 insertions(+), 674 deletions(-) delete mode 100644 crates/contract/tests/sandbox/ckd.rs diff --git a/crates/contract/tests/sandbox/ckd.rs b/crates/contract/tests/sandbox/ckd.rs deleted file mode 100644 index 280780e25e..0000000000 --- a/crates/contract/tests/sandbox/ckd.rs +++ /dev/null @@ -1,374 +0,0 @@ -use crate::sandbox::common::{init_env, SandboxTestSetup}; -use crate::sandbox::utils::consts::PARTICIPANT_LEN; -use crate::sandbox::utils::shared_key_utils::{generate_random_app_public_key, SharedSecretKey}; -use crate::sandbox::utils::sign_utils::{ - create_response_ckd, derive_confidential_key_and_validate, -}; -use mpc_contract::primitives::domain::SignatureScheme; -use mpc_contract::{ - crypto_shared::CKDResponse, - errors, - primitives::{ckd::CKDRequestArgs, domain::DomainId}, -}; -use near_account_id::AccountId; -use near_workspaces::{network::Sandbox, result::Execution, types::NearToken, Account, Worker}; -use rand::SeedableRng; -use rand_core::OsRng; -use utilities::{AccountIdExtV1, AccountIdExtV2}; - -async fn create_account_given_id( - worker: &Worker, - account_id: AccountId, -) -> Result, near_workspaces::error::Error> { - let (_, sk) = worker.generate_dev_account_credentials(); - worker - .create_root_account_subaccount(account_id.as_v1_account_id(), sk) - .await -} - -#[tokio::test] -async fn test_contract_ckd_request() -> anyhow::Result<()> { - let mut rng = rand::rngs::StdRng::from_seed([1u8; 32]); - let SandboxTestSetup { - worker, - contract, - mpc_signer_accounts, - keys, - } = init_env(&[SignatureScheme::Bls12381], PARTICIPANT_LEN).await; - - let attested_account = &mpc_signer_accounts[0]; - let SharedSecretKey::Bls12381(sk) = &keys[0].domain_secret_key else { - unreachable!(); - }; - - let account_ids: [AccountId; 4] = [ - "this_is_an_app".parse().unwrap(), - "another".parse().unwrap(), - "a_better_one".parse().unwrap(), - "a_fake_one".parse().unwrap(), - ]; - - let app_public_key = generate_random_app_public_key(&mut rng); - - for account_id in account_ids { - let account = create_account_given_id(&worker, account_id.clone()) - .await - .unwrap() - .unwrap(); - - println!("submitting: {account_id}"); - - let request = CKDRequestArgs { - derivation_path: "".to_string(), - app_public_key: app_public_key.clone(), - domain_id: DomainId::default(), - }; - - let (respond_req, respond_resp) = create_response_ckd( - &account.id().as_v2_account_id(), - app_public_key.clone(), - &request.domain_id, - sk, - "", - ); - - derive_confidential_key_and_validate( - account, - &request, - Some((&respond_req, &respond_resp)), - &contract, - attested_account, - ) - .await?; - } - - // check duplicate requests - let account_id: AccountId = "duplicate".parse().unwrap(); - let account = create_account_given_id(&worker, account_id.clone()) - .await - .unwrap() - .unwrap(); - let request = CKDRequestArgs { - derivation_path: "".to_string(), - app_public_key: app_public_key.clone(), - domain_id: DomainId::default(), - }; - let (respond_req, respond_resp) = create_response_ckd( - &account.id().as_v2_account_id(), - request.app_public_key.clone(), - &request.domain_id, - sk, - "", - ); - - derive_confidential_key_and_validate( - account.clone(), - &request, - Some((&respond_req, &respond_resp)), - &contract, - attested_account, - ) - .await?; - derive_confidential_key_and_validate( - account.clone(), - &request, - Some((&respond_req, &respond_resp)), - &contract, - attested_account, - ) - .await?; - - // Check that a ckd with no response from MPC network properly errors out: - let err = derive_confidential_key_and_validate( - account.clone(), - &request, - None, - &contract, - attested_account, - ) - .await - .expect_err("should have failed with timeout"); - assert!(err - .to_string() - .contains(&errors::RequestError::Timeout.to_string())); - - let request_with_path = CKDRequestArgs { - derivation_path: "this is a path".to_string(), - app_public_key: generate_random_app_public_key(&mut rng), - domain_id: DomainId::default(), - }; - - let (respond_request_with_path, respond_response_with_path) = create_response_ckd( - &account.id().as_v2_account_id(), - request_with_path.app_public_key.clone(), - &request_with_path.domain_id, - sk, - &request_with_path.derivation_path, - ); - - derive_confidential_key_and_validate( - account, - &request_with_path, - Some((&respond_request_with_path, &respond_response_with_path)), - &contract, - attested_account, - ) - .await?; - - Ok(()) -} - -#[tokio::test] -async fn test_contract_ckd_success_refund() -> anyhow::Result<()> { - let SandboxTestSetup { - worker, - contract, - mpc_signer_accounts, - keys, - } = init_env(&[SignatureScheme::Bls12381], PARTICIPANT_LEN).await; - let attested_account = &mpc_signer_accounts[0]; - - let alice = worker.dev_create_account().await?; - let balance = alice.view_account().await?.balance; - let contract_balance = contract.view_account().await?.balance; - let SharedSecretKey::Bls12381(sk) = &keys[0].domain_secret_key else { - unreachable!(); - }; - let app_public_key = generate_random_app_public_key(&mut OsRng); - let request = CKDRequestArgs { - derivation_path: "".to_string(), - app_public_key: app_public_key.clone(), - domain_id: DomainId::default(), - }; - - let (respond_req, respond_resp) = create_response_ckd( - &alice.id().as_v2_account_id(), - app_public_key, - &request.domain_id, - sk, - "", - ); - - let status = alice - .call(contract.id(), "request_app_private_key") - .args_json(serde_json::json!({ - "request": request, - })) - .deposit(NearToken::from_near(1)) - .max_gas() - .transact_async() - .await?; - dbg!(&status); - tokio::time::sleep(std::time::Duration::from_secs(3)).await; - - // Call `respond_ckd` as an attested node: - let respond = attested_account - .call(contract.id(), "respond_ckd") - .args_json(serde_json::json!({ - "request": respond_req, - "response": respond_resp - })) - .max_gas() - .transact() - .await?; - dbg!(&respond); - - let execution = status.await?; - dbg!(&execution); - - let execution = execution.into_result()?; - - // Finally wait the result: - let returned_resp: CKDResponse = execution.json()?; - assert_eq!( - returned_resp, respond_resp, - "Returned ckd request does not match" - ); - - let new_balance = alice.view_account().await?.balance; - let new_contract_balance = contract.view_account().await?.balance; - assert!( - balance.as_millinear() - new_balance.as_millinear() < 10, - "refund should happen" - ); - println!( - "{} {} {} {}", - balance.as_millinear(), - new_balance.as_millinear(), - contract_balance.as_millinear(), - new_contract_balance.as_millinear(), - ); - assert!( - contract_balance.as_millinear() <= new_contract_balance.as_millinear(), - "contract balance should not decrease after refunding deposit" - ); - - Ok(()) -} - -#[tokio::test] -async fn test_contract_ckd_fail_refund() -> anyhow::Result<()> { - let SandboxTestSetup { - worker, contract, .. - } = init_env(&[SignatureScheme::Bls12381], PARTICIPANT_LEN).await; - let alice = worker.dev_create_account().await?; - let balance = alice.view_account().await?.balance; - let contract_balance = contract.view_account().await?.balance; - let app_public_key = generate_random_app_public_key(&mut OsRng); - let request = CKDRequestArgs { - derivation_path: "".to_string(), - app_public_key, - domain_id: DomainId::default(), - }; - - let status = alice - .call(contract.id(), "request_app_private_key") - .args_json(serde_json::json!({ - "request": request, - })) - .deposit(NearToken::from_near(1)) - .max_gas() - .transact_async() - .await?; - dbg!(&status); - tokio::time::sleep(std::time::Duration::from_secs(3)).await; - - // we do not respond, ckd will fail due to timeout - let execution = status.await; - dbg!(&execution); - let err = execution - .unwrap() - .into_result() - .expect_err("should have failed with timeout"); - assert!(err - .to_string() - .contains(&errors::RequestError::Timeout.to_string())); - - let new_balance = alice.view_account().await?.balance; - let new_contract_balance = contract.view_account().await?.balance; - println!( - "{} {} {} {}", - balance.as_millinear(), - new_balance.as_millinear(), - contract_balance.as_yoctonear(), - new_contract_balance.as_yoctonear(), - ); - assert!(balance >= new_balance, "user balance should not increase"); - assert!( - balance.as_millinear() - new_balance.as_millinear() < 10, - "refund should happen" - ); - assert!( - contract_balance.as_millinear() <= new_contract_balance.as_millinear(), - "contract balance should not decrease after refunding deposit" - ); - - Ok(()) -} - -#[tokio::test] -async fn test_contract_ckd_request_deposits() -> anyhow::Result<()> { - let SandboxTestSetup { - worker, - contract, - mpc_signer_accounts, - keys, - } = init_env(&[SignatureScheme::Bls12381], PARTICIPANT_LEN).await; - let attested_account = &mpc_signer_accounts[0]; - - let alice = worker.dev_create_account().await?; - let SharedSecretKey::Bls12381(sk) = &keys[0].domain_secret_key else { - unreachable!(); - }; - let app_public_key = generate_random_app_public_key(&mut OsRng); - let request = CKDRequestArgs { - derivation_path: "".to_string(), - app_public_key: app_public_key.clone(), - domain_id: DomainId::default(), - }; - - let status = contract - .call("request_app_private_key") - .args_json(serde_json::json!({ - "request": request, - })) - .max_gas() - .transact_async() - .await?; - dbg!(&status); - - let (respond_req, respond_resp) = create_response_ckd( - &alice.id().as_v2_account_id(), - app_public_key, - &request.domain_id, - sk, - "", - ); - // Responding to the request should fail with missing request because the deposit is too low, - // so the request should have never made it into the request queue and subsequently the MPC network. - let respond = attested_account - .call(contract.id(), "respond_ckd") - .args_json(serde_json::json!({ - "request": respond_req, - "response": respond_resp - })) - .max_gas() - .transact() - .await?; - dbg!(&respond); - assert!(respond - .into_result() - .unwrap_err() - .to_string() - .contains(&errors::InvalidParameters::RequestNotFound.to_string())); - - let execution = status.await?; - dbg!(&execution); - assert!(execution - .into_result() - .unwrap_err() - .to_string() - .contains(&errors::InvalidParameters::InsufficientDeposit.to_string())); - - Ok(()) -} diff --git a/crates/contract/tests/sandbox/mod.rs b/crates/contract/tests/sandbox/mod.rs index 40e16a0324..38aebac521 100644 --- a/crates/contract/tests/sandbox/mod.rs +++ b/crates/contract/tests/sandbox/mod.rs @@ -1,4 +1,3 @@ -pub mod ckd; pub mod contract_configuration; pub mod sign; pub mod tee; diff --git a/crates/contract/tests/sandbox/sign.rs b/crates/contract/tests/sandbox/sign.rs index 498e606fef..9972138082 100644 --- a/crates/contract/tests/sandbox/sign.rs +++ b/crates/contract/tests/sandbox/sign.rs @@ -1,10 +1,14 @@ use crate::sandbox::{ common::{candidates, init, init_env, SandboxTestSetup}, utils::{ - consts::PARTICIPANT_LEN, - sign_utils::{submit_signature_response, SignRequestTest}, + consts::{ALL_SIGNATURE_SCHEMES, PARTICIPANT_LEN}, + shared_key_utils::SharedSecretKey, + sign_utils::{ + gen_secp_256k1_sign_test, submit_signature_response, verify_timeout, DomainResponseTest, + }, }, }; +use anyhow::Context; use mpc_contract::{ errors, primitives::{ @@ -13,112 +17,153 @@ use mpc_contract::{ thresholds::{Threshold, ThresholdParameters}, }, }; +use near_account_id::AccountId; use near_workspaces::types::NearToken; +use near_workspaces::{network::Sandbox, result::Execution, Account, Worker}; +use rand::SeedableRng; use std::time::Duration; use utilities::AccountIdExtV1; - -const NON_CKD_SCHEMES: &[SignatureScheme] = &[ - SignatureScheme::Secp256k1, - SignatureScheme::V2Secp256k1, - SignatureScheme::Ed25519, -]; +use utilities::AccountIdExtV2; const SIGNATURE_TIMEOUT_BLOCKS: u64 = 200; const NUM_BLOCKS_BETWEEN_REQUESTS: u64 = 2; +async fn create_account_given_id( + worker: &Worker, + account_id: AccountId, +) -> Result, near_workspaces::error::Error> { + let (_, sk) = worker.generate_dev_account_credentials(); + worker + .create_root_account_subaccount(account_id.as_v1_account_id(), sk) + .await +} + #[tokio::test] -async fn test_contract_sign_request_all_schemes() -> anyhow::Result<()> { +async fn test_contract_request_all_schemes() -> anyhow::Result<()> { + let mut rng = rand::rngs::StdRng::from_seed([1u8; 32]); let SandboxTestSetup { worker, contract, mpc_signer_accounts, keys, - } = init_env(NON_CKD_SCHEMES, PARTICIPANT_LEN).await; - + } = init_env(ALL_SIGNATURE_SCHEMES, PARTICIPANT_LEN).await; let attested_account = &mpc_signer_accounts[0]; - let path = "test"; - let alice = worker.dev_create_account().await.unwrap(); - let predecessor_id = alice.id(); - let messages = [ - "hello world", - "hello world!", - "hello world!!", - "hello world!!!", - "hello world!!!!", + + let account_ids: [AccountId; 5] = [ + "alice".parse().unwrap(), + "this_is_an_app".parse().unwrap(), + "another".parse().unwrap(), + "a_better_one".parse().unwrap(), + "a_fake_one".parse().unwrap(), ]; - for key in &keys { - for msg in messages { + for predecessor_id in account_ids { + let alice = create_account_given_id(&worker, predecessor_id) + .await + .unwrap() + .unwrap(); + let predecessor_id = alice.id(); + for key in &keys { { - println!("submitting: {msg}"); - let req = SignRequestTest::new(key, &predecessor_id.as_v2_account_id(), msg, path); - req.sign_and_validate(&alice, &contract, attested_account) + let req = + DomainResponseTest::new(&mut rng, key, &predecessor_id.as_v2_account_id()); + req.run(&alice, &contract, attested_account) .await + .with_context(|| format!("{:?}", req)) .unwrap(); } } + } + Ok(()) +} - { - // check that in case of duplicate request, only the most recent will be signed: - let msg = "welp"; - println!("submitting: {msg}"); - let req = SignRequestTest::new(key, &predecessor_id.as_v2_account_id(), msg, path); - let status_1 = req.sign_ensure_included(&alice, &contract).await?; - worker - .fast_forward(NUM_BLOCKS_BETWEEN_REQUESTS) - .await - .unwrap(); - let status_2 = req.sign_ensure_included(&alice, &contract).await?; - // unfortunately, we still can't completely get rid of this sleep - tokio::time::sleep(Duration::from_secs(3)).await; - worker - .fast_forward(NUM_BLOCKS_BETWEEN_REQUESTS) - .await - .unwrap(); - submit_signature_response(&req.response, &contract, attested_account).await?; - req.verify_execution_outcome(status_2) - .await - .expect("most recent signature request should succeed"); - worker.fast_forward(SIGNATURE_TIMEOUT_BLOCKS).await.unwrap(); - req.verify_timeout(status_1) - .await - .expect("initial signature request should time out"); - } +#[tokio::test] +async fn test_contract_request_duplicate_requests_all_schemes() -> anyhow::Result<()> { + let mut rng = rand::rngs::StdRng::from_seed([1u8; 32]); + let SandboxTestSetup { + worker, + contract, + mpc_signer_accounts, + keys, + } = init_env(ALL_SIGNATURE_SCHEMES, PARTICIPANT_LEN).await; + let attested_account = &mpc_signer_accounts[0]; - { - // Check that a sign with no response from MPC network properly errors out: - let msg = "this should timeout"; - println!("submitting: {msg}"); - let req = SignRequestTest::new(key, &predecessor_id.as_v2_account_id(), msg, path); - let status = req.sign_ensure_included(&alice, &contract).await?; - worker.fast_forward(SIGNATURE_TIMEOUT_BLOCKS).await.unwrap(); - req.verify_timeout(status).await.unwrap(); - } + for key in &keys { + let alice = worker.dev_create_account().await.unwrap(); + let predecessor_id = alice.id(); + // check that in case of duplicate request, only the most recent will be signed: + let req = DomainResponseTest::new(&mut rng, key, &predecessor_id.as_v2_account_id()); + let status_1 = req + .submit_request_ensure_included(&alice, &contract) + .await?; + worker + .fast_forward(NUM_BLOCKS_BETWEEN_REQUESTS) + .await + .unwrap(); + let status_2 = req + .submit_request_ensure_included(&alice, &contract) + .await?; + + // unfortunately, we still can't completely get rid of this sleep + tokio::time::sleep(Duration::from_secs(3)).await; + worker + .fast_forward(NUM_BLOCKS_BETWEEN_REQUESTS) + .await + .unwrap(); + req.submit_response(&contract, attested_account).await?; + req.verify_execution_outcome(status_2) + .await + .expect("most recent signature request should succeed"); + worker.fast_forward(SIGNATURE_TIMEOUT_BLOCKS).await.unwrap(); + verify_timeout(status_1) + .await + .expect("initial signature request should time out"); + } + Ok(()) +} + +#[tokio::test] +async fn test_contract_request_timeout_all_schemes() -> anyhow::Result<()> { + let mut rng = rand::rngs::StdRng::from_seed([1u8; 32]); + let SandboxTestSetup { + worker, + contract, + keys, + .. + } = init_env(ALL_SIGNATURE_SCHEMES, PARTICIPANT_LEN).await; + + for key in &keys { + let alice = worker.dev_create_account().await.unwrap(); + let predecessor_id = alice.id(); + // Check that a sign with no response from MPC network properly errors out: + let req = DomainResponseTest::new(&mut rng, key, &predecessor_id.as_v2_account_id()); + let status = req + .submit_request_ensure_included(&alice, &contract) + .await?; + worker.fast_forward(SIGNATURE_TIMEOUT_BLOCKS).await.unwrap(); + verify_timeout(status).await.unwrap(); } Ok(()) } #[tokio::test] -async fn test_contract_sign_success_refund() -> anyhow::Result<()> { +async fn test_contract_success_refund_all_schemes() -> anyhow::Result<()> { let SandboxTestSetup { worker, contract, mpc_signer_accounts, keys, - } = init_env(NON_CKD_SCHEMES, PARTICIPANT_LEN).await; + } = init_env(ALL_SIGNATURE_SCHEMES, PARTICIPANT_LEN).await; + let mut rng = rand::rngs::StdRng::from_seed([1u8; 32]); let attested_account = &mpc_signer_accounts[0]; let alice = worker.dev_create_account().await?; let balance = alice.view_account().await?.balance; let contract_balance = contract.view_account().await?.balance; - let path = "test"; - let msg = "hello world!"; for key in &keys { - println!("submitting: {msg}"); - let req = SignRequestTest::new(key, &alice.id().as_v2_account_id(), msg, path); - req.sign_and_validate(&alice, &contract, attested_account) - .await?; + let req = DomainResponseTest::new(&mut rng, key, &alice.id().as_v2_account_id()); + req.run(&alice, &contract, attested_account).await?; let new_balance = alice.view_account().await?.balance; let new_contract_balance = contract.view_account().await?.balance; @@ -140,26 +185,26 @@ async fn test_contract_sign_success_refund() -> anyhow::Result<()> { } #[tokio::test] -async fn test_contract_sign_fail_refund() -> anyhow::Result<()> { +async fn test_contract_fail_refund_all_schemes() -> anyhow::Result<()> { let SandboxTestSetup { worker, contract, keys, .. - } = init_env(NON_CKD_SCHEMES, PARTICIPANT_LEN).await; + } = init_env(ALL_SIGNATURE_SCHEMES, PARTICIPANT_LEN).await; + let mut rng = rand::rngs::StdRng::from_seed([2u8; 32]); let alice = worker.dev_create_account().await?; let balance = alice.view_account().await?.balance; let contract_balance = contract.view_account().await?.balance; - let path = "test"; - let msg = "hello world!"; - println!("submitting: {msg}"); for key in &keys { - let req = SignRequestTest::new(key, &alice.id().as_v2_account_id(), msg, path); - let status = req.sign_ensure_included(&alice, &contract).await?; + let req = DomainResponseTest::new(&mut rng, key, &alice.id().as_v2_account_id()); + let status = req + .submit_request_ensure_included(&alice, &contract) + .await?; worker.fast_forward(SIGNATURE_TIMEOUT_BLOCKS).await.unwrap(); // we do not respond, sign will fail due to timeout - req.verify_timeout(status).await?; + verify_timeout(status).await?; let new_balance = alice.view_account().await?.balance; let new_contract_balance = contract.view_account().await?.balance; @@ -177,32 +222,48 @@ async fn test_contract_sign_fail_refund() -> anyhow::Result<()> { } #[tokio::test] -async fn test_contract_sign_request_deposits() -> anyhow::Result<()> { +async fn test_contract_request_deposits_all_schemes() -> anyhow::Result<()> { let SandboxTestSetup { contract, mpc_signer_accounts, keys, .. - } = init_env(NON_CKD_SCHEMES, PARTICIPANT_LEN).await; + } = init_env(ALL_SIGNATURE_SCHEMES, PARTICIPANT_LEN).await; + let mut rng = rand::rngs::StdRng::from_seed([1u8; 32]); let attested_account = &mpc_signer_accounts[0]; let predecessor_id = contract.id(); - let path = "testing-no-deposit"; for key in &keys { // Try to sign with no deposit, should fail. - let msg = "without-deposit"; - let req = SignRequestTest::new(key, &predecessor_id.as_v2_account_id(), msg, path); - let status = contract - .call("sign") - .args_json(req.request_json_args()) - .max_gas() - .transact_async() - .await?; - dbg!(&status); + let req = DomainResponseTest::new(&mut rng, key, &predecessor_id.as_v2_account_id()); + let status = match &req { + DomainResponseTest::Sign(req) => { + let status = contract + .call("sign") + .args_json(req.request_json_args()) + .max_gas() + .transact_async() + .await?; + dbg!(&status); + status + } + DomainResponseTest::CKD(req) => { + let status = contract + .call("request_app_private_key") + .args_json(serde_json::json!({ + "request": req.args, + })) + .max_gas() + .transact_async() + .await?; + dbg!(&status); + status + } + }; // Responding to the request should fail with missing request because the deposit is too low, // so the request should have never made it into the request queue and subsequently the MPC network. - let respond = submit_signature_response(&req.response, &contract, attested_account).await; + let respond = req.submit_response(&contract, attested_account).await; dbg!(&respond); assert!(respond .unwrap_err() @@ -228,30 +289,31 @@ async fn test_sign_v1_compatibility() -> anyhow::Result<()> { keys, .. } = init_env(&[SignatureScheme::Secp256k1], PARTICIPANT_LEN).await; + let mut rng = rand::rngs::StdRng::from_seed([1u8; 32]); let key = &keys[0]; const LEGACY_KEY_VERSION: u64 = 0; // this is the first cait-sith domain in the contract + const NUM_MSGS: usize = 5; assert_eq!(key.domain_id().0, LEGACY_KEY_VERSION); + let SharedSecretKey::Secp256k1(sk) = &key.domain_secret_key else { + anyhow::bail!("expected secp256k1"); + }; let attested_account = &mpc_signer_accounts[0]; let predecessor_id = contract.id(); - let path = "test"; - let messages = [ - "hello world", - "hello world!", - "hello world!!", - "hello world!!!", - "hello world!!!!", - ]; + for _ in 0..NUM_MSGS { + let req = gen_secp_256k1_sign_test( + &mut rng, + key.domain_id(), + &predecessor_id.as_v2_account_id(), + sk, + ); - for msg in messages { - println!("submitting: {msg}"); - let req = SignRequestTest::new(key, &predecessor_id.as_v2_account_id(), msg, path); let status = contract .call("sign") .args_json(serde_json::json!({ "request": { "payload": req.payload().as_ecdsa().unwrap(), - "path": path, + "path": req.path(), "key_version": LEGACY_KEY_VERSION, }, })) diff --git a/crates/contract/tests/sandbox/upgrade_to_current_contract.rs b/crates/contract/tests/sandbox/upgrade_to_current_contract.rs index 2646027949..580d5e1941 100644 --- a/crates/contract/tests/sandbox/upgrade_to_current_contract.rs +++ b/crates/contract/tests/sandbox/upgrade_to_current_contract.rs @@ -389,20 +389,15 @@ async fn upgrade_allows_new_request_types( } for pending in pending_ckd_requests { - submit_ckd_response( - &pending.ckd_request, - &pending.ckd_response, - &contract, - attested_account, - ) - .await - .unwrap(); + submit_ckd_response(&pending.ckd_response, &contract, attested_account) + .await + .unwrap(); let execution = pending.transaction.await.unwrap().into_result().unwrap(); let returned: CKDResponse = execution.json().unwrap(); assert_eq!( - returned, pending.ckd_response, + returned, pending.ckd_response.response, "Returned ckd response does not match" ); } diff --git a/crates/contract/tests/sandbox/utils/sign_utils.rs b/crates/contract/tests/sandbox/utils/sign_utils.rs index 56fef687c7..95f3358cc0 100644 --- a/crates/contract/tests/sandbox/utils/sign_utils.rs +++ b/crates/contract/tests/sandbox/utils/sign_utils.rs @@ -18,7 +18,7 @@ use mpc_contract::{ errors, primitives::{ ckd::{CKDRequest, CKDRequestArgs}, - domain::{DomainId, SignatureScheme}, + domain::DomainId, signature::{Bytes, Payload, SignRequestArgs, SignatureRequest, YieldIndex}, }, }; @@ -26,7 +26,7 @@ use near_account_id::AccountId; use near_workspaces::{ network::Sandbox, operations::TransactionStatus, types::NearToken, Account, Contract, Worker, }; -use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; +use rand::{rngs::OsRng, Rng}; use rand_core::CryptoRngCore; use serde::Serialize; use sha2::Sha256; @@ -40,59 +40,98 @@ use threshold_signatures::{ }; use utilities::AccountIdExtV1; -#[derive(Serialize)] -pub struct SignResponseArgs { - pub request: SignatureRequest, - pub response: SignatureResponse, -} - -impl SignResponseArgs { - pub fn json_args(&self) -> serde_json::Value { - serde_json::to_value(self).unwrap() - } -} - -pub struct SignRequestTest { - pub response: SignResponseArgs, - pub args: SignRequestArgs, +#[derive(Debug)] +pub enum DomainResponseTest { + Sign(SignRequestTest), + CKD(CKDRequestTest), } -impl SignRequestTest { +impl DomainResponseTest { pub fn new( + rng: &mut impl CryptoRngCore, domain_key: &DomainKey, predecessor_id: &AccountId, - msg: &str, - path: &str, - ) -> SignRequestTest { + ) -> Self { let domain_id = domain_key.domain_config.id; - let (payload, request, response) = match &domain_key.domain_secret_key { - SharedSecretKey::Secp256k1(sk) => { - create_response_secp256k1(domain_id, predecessor_id, msg, path, sk) - } + match &domain_key.domain_secret_key { + SharedSecretKey::Secp256k1(sk) => DomainResponseTest::Sign(gen_secp_256k1_sign_test( + rng, + domain_id, + predecessor_id, + sk, + )), SharedSecretKey::Ed25519(sk) => { - create_response_ed25519(domain_id, predecessor_id, msg, path, sk) + DomainResponseTest::Sign(gen_ed25519_sign_test(rng, domain_id, predecessor_id, sk)) } - SharedSecretKey::Bls12381(_) => { - // todo: make SignRequestTest an enum - unreachable!() + SharedSecretKey::Bls12381(sk) => { + DomainResponseTest::CKD(CKDRequestTest::new(rng, domain_id, predecessor_id, sk)) } - }; - let args = SignRequestArgs { - payload_v2: Some(payload.clone()), - path: path.into(), - domain_id: Some(domain_key.domain_id()), - ..Default::default() - }; - SignRequestTest { - response: SignResponseArgs { request, response }, - args, } } - pub fn expected_response(&self) -> &SignatureResponse { - &self.response.response + pub async fn run( + &self, + account: &Account, + contract: &Contract, + attested_account: &Account, + ) -> anyhow::Result<()> { + let status = self + .submit_request_ensure_included(account, contract) + .await?; + self.submit_response(contract, attested_account).await?; + self.verify_execution_outcome(status).await?; + Ok(()) } + pub async fn submit_response( + &self, + contract: &Contract, + attested_account: &Account, + ) -> anyhow::Result<()> { + match &self { + Self::Sign(inner) => { + submit_signature_response(&inner.response, contract, attested_account).await + } + Self::CKD(inner) => { + submit_ckd_response(&inner.response, contract, attested_account).await + } + } + } + + pub async fn submit_request_ensure_included( + &self, + account: &Account, + contract: &Contract, + ) -> anyhow::Result { + match self { + Self::Sign(inner) => { + let status = submit_sign_request(account, &inner.args, contract).await?; + await_request_in_contract_queue(contract, &inner.response.request, None).await?; + Ok(status) + } + Self::CKD(inner) => { + let status = submit_ckd_request(account, &inner.args, contract).await?; + await_request_in_contract_queue(contract, &inner.response.request, None).await?; + Ok(status) + } + } + } + + pub async fn verify_execution_outcome(&self, status: TransactionStatus) -> anyhow::Result<()> { + match self { + Self::Sign(inner) => inner.verify_execution_outcome(status).await, + Self::CKD(inner) => inner.verify_execution_outcome(status).await, + } + } +} + +#[derive(Debug)] +pub struct SignRequestTest { + pub response: SignResponseArgs, + pub args: SignRequestArgs, +} + +impl SignRequestTest { pub fn request_json_args(&self) -> serde_json::Value { serde_json::json!({ "request": self.args, @@ -103,55 +142,98 @@ impl SignRequestTest { &self.response.request.payload } + pub fn path(&self) -> &String { + &self.args.path + } + pub async fn verify_execution_outcome(&self, status: TransactionStatus) -> anyhow::Result<()> { let execution = status.await?; dbg!(&execution); let execution = execution.into_result()?; let returned_resp: SignatureResponse = execution.json()?; assert_eq!( - &returned_resp, &self.response.response, + returned_resp, self.response.response, "Returned signature request does not match" ); Ok(()) } - pub async fn verify_timeout(&self, status: TransactionStatus) -> anyhow::Result<()> { - let execution = status.await?; - dbg!(&execution); - assert!(execution.is_failure()); - let err = execution - .into_result() - .expect_err("expect execution failure"); - assert!(err - .to_string() - .contains(&errors::RequestError::Timeout.to_string())); - Ok(()) - } - - pub async fn sign_ensure_included( + pub async fn submit_response( &self, - account: &Account, - contract: &Contract, - ) -> anyhow::Result { - let status = submit_sign_request(account, &self.args, contract).await?; - await_request_in_contract_queue(contract, &self.response.request, None).await?; - Ok(status) - } - - pub async fn sign_and_validate( - &self, - account: &Account, contract: &Contract, attested_account: &Account, ) -> anyhow::Result<()> { - let status = self.sign_ensure_included(account, contract).await?; - submit_signature_response(&self.response, contract, attested_account).await?; - self.verify_execution_outcome(status).await + submit_signature_response(&self.response, contract, attested_account).await + } +} + +#[derive(Debug, Serialize)] +pub struct SignResponseArgs { + pub request: SignatureRequest, + pub response: SignatureResponse, +} + +impl SignResponseArgs { + pub fn json_args(&self) -> serde_json::Value { + serde_json::to_value(self).unwrap() + } +} + +pub async fn verify_timeout(status: TransactionStatus) -> anyhow::Result<()> { + let execution = status.await?; + dbg!(&execution); + assert!(execution.is_failure()); + let err = execution + .into_result() + .expect_err("expect execution failure"); + assert!(err + .to_string() + .contains(&errors::RequestError::Timeout.to_string())); + Ok(()) +} + +#[derive(Debug)] +pub struct CKDRequestTest { + pub response: CKDResponseArgs, + pub args: CKDRequestArgs, +} + +impl CKDRequestTest { + pub fn new( + rng: &mut impl CryptoRngCore, + domain_id: DomainId, + predecessor_id: &AccountId, + sk: &ckd::KeygenOutput, + ) -> CKDRequestTest { + let derivation_path: String = rng.gen::().to_string(); + let app_public_key = generate_random_app_public_key(rng); + let (request, response) = create_response_ckd( + predecessor_id, + app_public_key.clone(), + &domain_id, + sk, + &derivation_path, + ); + let args = CKDRequestArgs { + derivation_path: derivation_path.to_string(), + app_public_key: app_public_key.clone(), + domain_id, + }; + + CKDRequestTest { + response: CKDResponseArgs { request, response }, + args, + } } } -// contract interactions -pub async fn submit_sign_request( +#[derive(Debug)] +pub struct CKDResponseArgs { + pub request: CKDRequest, + pub response: CKDResponse, +} + +async fn submit_sign_request( account: &Account, request: &SignRequestArgs, contract: &Contract, @@ -166,11 +248,10 @@ pub async fn submit_sign_request( .transact_async() .await?; dbg!(&status); - Ok(status) } -pub async fn submit_ckd_request( +async fn submit_ckd_request( account: &Account, request: &CKDRequestArgs, contract: &Contract, @@ -185,7 +266,6 @@ pub async fn submit_ckd_request( .transact_async() .await?; dbg!(&status); - Ok(status) } @@ -207,8 +287,7 @@ pub async fn submit_signature_response( } pub async fn submit_ckd_response( - respond_req: &CKDRequest, - respond_resp: &CKDResponse, + response: &CKDResponseArgs, contract: &Contract, attested_account: &Account, ) -> anyhow::Result<()> { @@ -216,41 +295,55 @@ pub async fn submit_ckd_response( let respond = attested_account .call(contract.id(), "respond_ckd") .args_json(serde_json::json!({ - "request": respond_req, - "response": respond_resp + "request": response.request, + "response": response.response, })) .max_gas() .transact() .await?; dbg!(&respond); - + respond.into_result()?; Ok(()) } -// contract state queries -pub async fn request_is_in_queue( - contract: &Contract, - request: &SignatureRequest, -) -> Option { - contract - .view("get_pending_request") - .args_json(serde_json::json!({"request": request})) - .await - .unwrap() - .json() - .unwrap() +trait ContractQueueRequest: serde::Serialize + Sync { + async fn is_in_queue(&self, contract: &Contract) -> Option; } -pub async fn await_request_in_contract_queue( +impl ContractQueueRequest for CKDRequest { + async fn is_in_queue(&self, contract: &Contract) -> Option { + contract + .view("get_pending_ckd_request") + .args_json(serde_json::json!({ "request": self })) + .await + .unwrap() + .json() + .unwrap() + } +} + +impl ContractQueueRequest for SignatureRequest { + async fn is_in_queue(&self, contract: &Contract) -> Option { + contract + .view("get_pending_request") + .args_json(serde_json::json!({ "request": self })) + .await + .unwrap() + .json() + .unwrap() + } +} + +async fn await_request_in_contract_queue( contract: &Contract, - request: &SignatureRequest, + request: &T, max_timeout: Option, ) -> anyhow::Result<()> { let timeout = max_timeout.unwrap_or(DEFAULT_MAX_TIMEOUT_TX_INCLUDED); let start = std::time::Instant::now(); loop { - if request_is_in_queue(contract, request).await.is_some() { + if request.is_in_queue(contract).await.is_some() { return Ok(()); } @@ -263,7 +356,7 @@ pub async fn await_request_in_contract_queue( } /// Derives a confidential key following https://github.com/near/threshold-signatures/blob/main/docs/confidential_key_derivation.md -pub fn create_response_ckd( +fn create_response_ckd( account_id: &AccountId, app_public_key: dtos::Bls12381G1PublicKey, domain_id: &DomainId, @@ -293,54 +386,18 @@ pub fn create_response_ckd( (request, response) } -// contract calls -pub async fn derive_confidential_key_and_validate( - account: Account, - request: &CKDRequestArgs, - respond: Option<(&CKDRequest, &CKDResponse)>, - contract: &Contract, - attested_account: &Account, -) -> anyhow::Result<()> { - let status = account - .call(contract.id(), "request_app_private_key") - .args_json(serde_json::json!({ "request": request })) - .deposit(NearToken::from_yoctonear(1)) - .max_gas() - .transact_async() - .await?; - dbg!(&status); - tokio::time::sleep(std::time::Duration::from_secs(3)).await; - - if let Some((respond_req, respond_resp)) = respond { - assert_eq!( - derive_app_id(&account.id().as_v2_account_id(), &request.derivation_path), - respond_req.app_id - ); - let respond = attested_account - .call(contract.id(), "respond_ckd") - .args_json(serde_json::json!({ - "request": respond_req, - "response": respond_resp - })) - .max_gas() - .transact() - .await?; - dbg!(&respond); - } - - let execution = status.await?; - dbg!(&execution); - let execution = execution.into_result()?; - - // Finally wait the result: - let returned_resp: CKDResponse = execution.json()?; - if let Some((_, respond_resp)) = respond { +impl CKDRequestTest { + async fn verify_execution_outcome(&self, status: TransactionStatus) -> anyhow::Result<()> { + let execution = status.await?; + dbg!(&execution); + let execution = execution.into_result()?; + let returned_resp: CKDResponse = execution.json()?; assert_eq!( - &returned_resp, respond_resp, + returned_resp, self.response.response, "Returned ckd request does not match" ); + Ok(()) } - Ok(()) } pub async fn make_and_submit_requests( @@ -351,61 +408,31 @@ pub async fn make_and_submit_requests( ) -> (Vec, Vec) { let mut pending_sign_requests = vec![]; let mut pending_ckd_requests = vec![]; - let path = "test"; - let signature_request_payloads = [ - generate_random_request_payloads(10, rng), - generate_random_request_payloads(4, rng), - ]; - let app_public_keys = [ - generate_random_app_public_key(rng), - generate_random_app_public_key(rng), - ]; + const NUM_TESTS: usize = 2; let alice = worker.dev_create_account().await.unwrap(); let alice_id = alice.id().as_v2_account_id(); for key in keys { - match key.domain_config.scheme { - SignatureScheme::Secp256k1 - | SignatureScheme::Ed25519 - | SignatureScheme::V2Secp256k1 => { - for message in &signature_request_payloads { - let req = SignRequestTest::new(key, &alice_id, message, path); - let transaction = submit_sign_request(&alice, &req.args, contract) + for _ in 0..NUM_TESTS { + match DomainResponseTest::new(rng, key, &alice_id) { + DomainResponseTest::Sign(inner) => { + let transaction = submit_sign_request(&alice, &inner.args, contract) .await .unwrap(); pending_sign_requests.push(PendingSignRequest { transaction, - response: req.response, + response: inner.response, }); } - } - SignatureScheme::Bls12381 => { - for app_public_key in &app_public_keys { - let SharedSecretKey::Bls12381(sk) = &key.domain_secret_key else { - unreachable!(); - }; - let (ckd_request, ckd_response) = create_response_ckd( - &alice_id, - app_public_key.clone(), - &key.domain_id(), - sk, - path, - ); - let request_args = CKDRequestArgs { - derivation_path: path.to_string(), - app_public_key: app_public_key.clone(), - domain_id: key.domain_id(), - }; - let transaction = submit_ckd_request(&alice, &request_args, contract) + DomainResponseTest::CKD(inner) => { + let transaction = submit_ckd_request(&alice, &inner.args, contract) .await .unwrap(); - pending_ckd_requests.push(PendingCKDRequest { transaction, - ckd_request, - ckd_response, + ckd_response: inner.response, }); } } @@ -414,11 +441,7 @@ pub async fn make_and_submit_requests( (pending_sign_requests, pending_ckd_requests) } -fn generate_random_request_payloads(n: usize, rng: &mut impl CryptoRngCore) -> String { - (0..n).map(|_| rng.sample(Alphanumeric) as char).collect() -} - -pub fn create_response_secp256k1( +fn create_response_secp256k1( domain_id: DomainId, predecessor_id: &AccountId, msg: &str, @@ -427,7 +450,6 @@ pub fn create_response_secp256k1( ) -> (Payload, SignatureRequest, SignatureResponse) { let (digest, payload) = process_message(msg); let pk = signing_key.public_key; - let tweak = derive_tweak(predecessor_id, path); let derived_sk = derive_secret_key_secp256k1(signing_key, &tweak); let derived_pk = derive_key_secp256k1(&pk.to_element().to_affine(), &tweak).unwrap(); @@ -468,7 +490,7 @@ pub fn create_response_secp256k1( (payload, respond_req, respond_resp) } -pub fn create_response_ed25519( +fn create_response_ed25519( domain_id: DomainId, predecessor_id: &AccountId, msg: &str, @@ -508,7 +530,7 @@ pub fn create_response_ed25519( } /// Process the message, creating the same hash with type of [`Digest`] and [`Payload`] -pub fn process_message(msg: &str) -> (impl Digest, Payload) { +fn process_message(msg: &str) -> (impl Digest, Payload) { let msg = msg.as_bytes(); let digest = ::Digest::new_with_prefix(msg); let bytes: FieldBytes = digest.clone().finalize_fixed(); @@ -524,6 +546,49 @@ pub struct PendingSignRequest { pub struct PendingCKDRequest { pub transaction: TransactionStatus, - pub ckd_request: CKDRequest, - pub ckd_response: CKDResponse, + pub ckd_response: CKDResponseArgs, +} + +fn gen_ed25519_sign_test( + rng: &mut impl Rng, + domain_id: DomainId, + predecessor_id: &AccountId, + sk: &eddsa::KeygenOutput, +) -> SignRequestTest { + let msg: String = rng.gen::().to_string(); + let path: String = rng.gen::().to_string(); + let (payload, request, response) = + create_response_ed25519(domain_id, predecessor_id, &msg, &path, sk); + let args = SignRequestArgs { + payload_v2: Some(payload.clone()), + path, + domain_id: Some(domain_id), + ..Default::default() + }; + SignRequestTest { + response: SignResponseArgs { request, response }, + args, + } +} + +pub fn gen_secp_256k1_sign_test( + rng: &mut impl Rng, + domain_id: DomainId, + predecessor_id: &AccountId, + sk: &ts_ecdsa::KeygenOutput, +) -> SignRequestTest { + let msg: String = rng.gen::().to_string(); + let path: String = rng.gen::().to_string(); + let (payload, request, response) = + create_response_secp256k1(domain_id, predecessor_id, &msg, &path, sk); + let args = SignRequestArgs { + payload_v2: Some(payload.clone()), + path, + domain_id: Some(domain_id), + ..Default::default() + }; + SignRequestTest { + response: SignResponseArgs { request, response }, + args, + } } From ccdf539f2c0ffc33b286bb0e11bf016217f8e7a2 Mon Sep 17 00:00:00 2001 From: Kevin Deforth <32777623+kevindeforth@users.noreply.github.com.> Date: Mon, 12 Jan 2026 10:27:22 +0100 Subject: [PATCH 2/7] cleanup --- crates/contract/tests/sandbox/common.rs | 15 +++++++++++++-- crates/contract/tests/sandbox/sign.rs | 19 +++---------------- .../tests/sandbox/utils/sign_utils.rs | 5 +++++ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/crates/contract/tests/sandbox/common.rs b/crates/contract/tests/sandbox/common.rs index cd61de372f..6e490eb801 100644 --- a/crates/contract/tests/sandbox/common.rs +++ b/crates/contract/tests/sandbox/common.rs @@ -24,11 +24,22 @@ use mpc_contract::{ update::{ProposeUpdateArgs, UpdateId}, }; use near_account_id::AccountId; -use near_workspaces::{network::Sandbox, Account, Contract, Worker}; +use near_workspaces::{network::Sandbox, Contract}; +use near_workspaces::{result::Execution, Account, Worker}; use rand_core::CryptoRngCore; use serde_json::json; use std::collections::BTreeSet; -use utilities::AccountIdExtV1; +use utilities::{AccountIdExtV1, AccountIdExtV2}; + +pub async fn create_account_given_id( + worker: &Worker, + account_id: AccountId, +) -> Result, near_workspaces::error::Error> { + let (_, sk) = worker.generate_dev_account_credentials(); + worker + .create_root_account_subaccount(account_id.as_v1_account_id(), sk) + .await +} pub fn gen_participant_info() -> ParticipantInfo { ParticipantInfo { diff --git a/crates/contract/tests/sandbox/sign.rs b/crates/contract/tests/sandbox/sign.rs index 9972138082..377ab08270 100644 --- a/crates/contract/tests/sandbox/sign.rs +++ b/crates/contract/tests/sandbox/sign.rs @@ -1,5 +1,5 @@ use crate::sandbox::{ - common::{candidates, init, init_env, SandboxTestSetup}, + common::{candidates, create_account_given_id, init, init_env, SandboxTestSetup}, utils::{ consts::{ALL_SIGNATURE_SCHEMES, PARTICIPANT_LEN}, shared_key_utils::SharedSecretKey, @@ -19,25 +19,13 @@ use mpc_contract::{ }; use near_account_id::AccountId; use near_workspaces::types::NearToken; -use near_workspaces::{network::Sandbox, result::Execution, Account, Worker}; use rand::SeedableRng; use std::time::Duration; use utilities::AccountIdExtV1; -use utilities::AccountIdExtV2; const SIGNATURE_TIMEOUT_BLOCKS: u64 = 200; const NUM_BLOCKS_BETWEEN_REQUESTS: u64 = 2; -async fn create_account_given_id( - worker: &Worker, - account_id: AccountId, -) -> Result, near_workspaces::error::Error> { - let (_, sk) = worker.generate_dev_account_credentials(); - worker - .create_root_account_subaccount(account_id.as_v1_account_id(), sk) - .await -} - #[tokio::test] async fn test_contract_request_all_schemes() -> anyhow::Result<()> { let mut rng = rand::rngs::StdRng::from_seed([1u8; 32]); @@ -105,6 +93,7 @@ async fn test_contract_request_duplicate_requests_all_schemes() -> anyhow::Resul .await?; // unfortunately, we still can't completely get rid of this sleep + // todo: [#1307](https://github.com/near/mpc/issues/1306) tokio::time::sleep(Duration::from_secs(3)).await; worker .fast_forward(NUM_BLOCKS_BETWEEN_REQUESTS) @@ -250,9 +239,7 @@ async fn test_contract_request_deposits_all_schemes() -> anyhow::Result<()> { DomainResponseTest::CKD(req) => { let status = contract .call("request_app_private_key") - .args_json(serde_json::json!({ - "request": req.args, - })) + .args_json(req.request_json_args()) .max_gas() .transact_async() .await?; diff --git a/crates/contract/tests/sandbox/utils/sign_utils.rs b/crates/contract/tests/sandbox/utils/sign_utils.rs index 95f3358cc0..9faeb48ab3 100644 --- a/crates/contract/tests/sandbox/utils/sign_utils.rs +++ b/crates/contract/tests/sandbox/utils/sign_utils.rs @@ -225,6 +225,11 @@ impl CKDRequestTest { args, } } + pub fn request_json_args(&self) -> serde_json::Value { + serde_json::json!({ + "request": self.args, + }) + } } #[derive(Debug)] From abec105c58f32b27d3e4c4c3de65c116aabcc644 Mon Sep 17 00:00:00 2001 From: kevindeforth <32777623+kevindeforth@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:25:13 +0100 Subject: [PATCH 3/7] Update crates/contract/tests/sandbox/sign.rs Co-authored-by: Reynaldo Gil Pons --- crates/contract/tests/sandbox/sign.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/contract/tests/sandbox/sign.rs b/crates/contract/tests/sandbox/sign.rs index 377ab08270..686cd4b9cc 100644 --- a/crates/contract/tests/sandbox/sign.rs +++ b/crates/contract/tests/sandbox/sign.rs @@ -93,7 +93,7 @@ async fn test_contract_request_duplicate_requests_all_schemes() -> anyhow::Resul .await?; // unfortunately, we still can't completely get rid of this sleep - // todo: [#1307](https://github.com/near/mpc/issues/1306) + // TODO(#1306): remove the need to sleep tokio::time::sleep(Duration::from_secs(3)).await; worker .fast_forward(NUM_BLOCKS_BETWEEN_REQUESTS) From df27d57a1c0abdb7a7c912074f13c7b3e20c3e69 Mon Sep 17 00:00:00 2001 From: kevindeforth <32777623+kevindeforth@users.noreply.github.com> Date: Mon, 12 Jan 2026 19:59:57 +0100 Subject: [PATCH 4/7] copilot nits --- crates/contract/tests/sandbox/utils/sign_utils.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/contract/tests/sandbox/utils/sign_utils.rs b/crates/contract/tests/sandbox/utils/sign_utils.rs index 9faeb48ab3..8c152df703 100644 --- a/crates/contract/tests/sandbox/utils/sign_utils.rs +++ b/crates/contract/tests/sandbox/utils/sign_utils.rs @@ -198,6 +198,15 @@ pub struct CKDRequestTest { pub args: CKDRequestArgs, } +fn gen_ckd_derivation_path(rng: &mut impl CryptoRngCore) -> String { + let empty: bool = rng.gen(); + if empty { + "".to_string() + } else { + rng.gen::().to_string() + } +} + impl CKDRequestTest { pub fn new( rng: &mut impl CryptoRngCore, @@ -205,7 +214,7 @@ impl CKDRequestTest { predecessor_id: &AccountId, sk: &ckd::KeygenOutput, ) -> CKDRequestTest { - let derivation_path: String = rng.gen::().to_string(); + let derivation_path = gen_ckd_derivation_path(rng); let app_public_key = generate_random_app_public_key(rng); let (request, response) = create_response_ckd( predecessor_id, @@ -215,8 +224,8 @@ impl CKDRequestTest { &derivation_path, ); let args = CKDRequestArgs { - derivation_path: derivation_path.to_string(), - app_public_key: app_public_key.clone(), + derivation_path, + app_public_key, domain_id, }; From 82399cc4ad621bbb2a21bdbc026d064e3ca00917 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 13 Jan 2026 12:27:39 +0100 Subject: [PATCH 5/7] less copy-paste (#1747) --- .../tests/sandbox/utils/sign_utils.rs | 65 +++++++++---------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/crates/contract/tests/sandbox/utils/sign_utils.rs b/crates/contract/tests/sandbox/utils/sign_utils.rs index 8c152df703..277eeb7106 100644 --- a/crates/contract/tests/sandbox/utils/sign_utils.rs +++ b/crates/contract/tests/sandbox/utils/sign_utils.rs @@ -241,22 +241,21 @@ impl CKDRequestTest { } } -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct CKDResponseArgs { pub request: CKDRequest, pub response: CKDResponse, } -async fn submit_sign_request( +async fn submit_request( account: &Account, - request: &SignRequestArgs, contract: &Contract, + method: &str, + args: impl Serialize, ) -> anyhow::Result { let status = account - .call(contract.id(), "sign") - .args_json(serde_json::json!({ - "request": request, - })) + .call(contract.id(), method) + .args_json(serde_json::json!({ "request": args })) .deposit(NearToken::from_yoctonear(1)) .max_gas() .transact_async() @@ -265,33 +264,31 @@ async fn submit_sign_request( Ok(status) } +async fn submit_sign_request( + account: &Account, + request: &SignRequestArgs, + contract: &Contract, +) -> anyhow::Result { + submit_request(account, contract, "sign", request).await +} + async fn submit_ckd_request( account: &Account, request: &CKDRequestArgs, contract: &Contract, ) -> anyhow::Result { - let status = account - .call(contract.id(), "request_app_private_key") - .args_json(serde_json::json!({ - "request": request, - })) - .deposit(NearToken::from_yoctonear(1)) - .max_gas() - .transact_async() - .await?; - dbg!(&status); - Ok(status) + submit_request(account, contract, "request_app_private_key", request).await } -pub async fn submit_signature_response( - response: &SignResponseArgs, +async fn submit_response( contract: &Contract, attested_account: &Account, + method: &str, + args: impl Serialize, ) -> anyhow::Result<()> { - // Call `respond` as if we are an attested_account let respond = attested_account - .call(contract.id(), "respond") - .args_json(response.json_args()) + .call(contract.id(), method) + .args_json(args) .max_gas() .transact() .await?; @@ -300,24 +297,20 @@ pub async fn submit_signature_response( Ok(()) } +pub async fn submit_signature_response( + response: &SignResponseArgs, + contract: &Contract, + attested_account: &Account, +) -> anyhow::Result<()> { + submit_response(contract, attested_account, "respond", response).await +} + pub async fn submit_ckd_response( response: &CKDResponseArgs, contract: &Contract, attested_account: &Account, ) -> anyhow::Result<()> { - // Call `respond` as if we are an attested_account - let respond = attested_account - .call(contract.id(), "respond_ckd") - .args_json(serde_json::json!({ - "request": response.request, - "response": response.response, - })) - .max_gas() - .transact() - .await?; - dbg!(&respond); - respond.into_result()?; - Ok(()) + submit_response(contract, attested_account, "respond_ckd", response).await } trait ContractQueueRequest: serde::Serialize + Sync { From 5cafb3c7c83ded3a7f1b5a2ae614377d0dfa37f0 Mon Sep 17 00:00:00 2001 From: kevindeforth Date: Tue, 13 Jan 2026 12:30:58 +0100 Subject: [PATCH 6/7] str --- crates/contract/tests/sandbox/utils/sign_utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/contract/tests/sandbox/utils/sign_utils.rs b/crates/contract/tests/sandbox/utils/sign_utils.rs index 277eeb7106..6df160e7ad 100644 --- a/crates/contract/tests/sandbox/utils/sign_utils.rs +++ b/crates/contract/tests/sandbox/utils/sign_utils.rs @@ -142,7 +142,7 @@ impl SignRequestTest { &self.response.request.payload } - pub fn path(&self) -> &String { + pub fn path(&self) -> &str { &self.args.path } From 9299148d0b9bd36e01c003520a5a0b9312d2db34 Mon Sep 17 00:00:00 2001 From: kevindeforth Date: Tue, 13 Jan 2026 12:32:49 +0100 Subject: [PATCH 7/7] impl --- .../tests/sandbox/utils/sign_utils.rs | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/crates/contract/tests/sandbox/utils/sign_utils.rs b/crates/contract/tests/sandbox/utils/sign_utils.rs index 6df160e7ad..ea749ea643 100644 --- a/crates/contract/tests/sandbox/utils/sign_utils.rs +++ b/crates/contract/tests/sandbox/utils/sign_utils.rs @@ -239,6 +239,18 @@ impl CKDRequestTest { "request": self.args, }) } + + async fn verify_execution_outcome(&self, status: TransactionStatus) -> anyhow::Result<()> { + let execution = status.await?; + dbg!(&execution); + let execution = execution.into_result()?; + let returned_resp: CKDResponse = execution.json()?; + assert_eq!( + returned_resp, self.response.response, + "Returned ckd request does not match" + ); + Ok(()) + } } #[derive(Debug, Serialize)] @@ -393,20 +405,6 @@ fn create_response_ckd( (request, response) } -impl CKDRequestTest { - async fn verify_execution_outcome(&self, status: TransactionStatus) -> anyhow::Result<()> { - let execution = status.await?; - dbg!(&execution); - let execution = execution.into_result()?; - let returned_resp: CKDResponse = execution.json()?; - assert_eq!( - returned_resp, self.response.response, - "Returned ckd request does not match" - ); - Ok(()) - } -} - pub async fn make_and_submit_requests( keys: &[DomainKey], contract: &Contract,