diff --git a/API.md b/API.md index 5e88de20..c44a3594 100644 --- a/API.md +++ b/API.md @@ -55,4 +55,10 @@ For more details check `User contract API` impl block in the [chain-signatures/c # Environments 1. Mainnet: `v1.signer` -2. Testnet: `v1.sigenr-prod.testnet` \ No newline at end of file +2. Testnet: `v1.sigenr-prod.testnet` + +# Interact using NEAR CLI + +There is an `Example Commands` in the [chain-signatures/contract/example.md](./chain-signature/contract/EXAMPLE.md) file. + +These commands were tested on [near-cli](https://github.com/near/near-cli) 4.0.0. diff --git a/chain-signatures/contract/EXAMPLE.md b/chain-signatures/contract/EXAMPLE.md new file mode 100644 index 00000000..0f05ed43 --- /dev/null +++ b/chain-signatures/contract/EXAMPLE.md @@ -0,0 +1,49 @@ +# Iteracting with contract using NEAR CLI +All data is fake and used for example purposes +It's necessary to update script after contract API changes +## User contract API + +near call v1.signer-dev.testnet sign '{"request":{"key_version":0,"path":"test","payload":[12,1,2,0,4,5,6,8,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,44]}}' --accountId alexkushnir.testnet --gas 300000000000000 --deposit 1 + +near view v1.signer-dev.testnet public_key + +near view v1.signer-dev.testnet derived_public_key {"path":"test","predecessor":"alexkushnir.testnet"} + +near view v1.signer-dev.testnet latest_key_version + +near view v1.signer-dev.testnet experimental_signature_deposit + + +## Node API + +near call v1.signer-dev.testnet respond '{"request":{"epsilon":{"scalar":"72DB59A313FA266B1C3B40F20325C9023DDC564E7790363BCC2AE76580339648"},"payload_hash":{"scalar":"05FCB4470106774DCC5A3C7689FD2917C15AB81B6FA44960843E6389780A5364"}},"response":{"big_r":{"affine_point":"02EC7FA686BB430A4B700BDA07F2E07D6333D9E33AEEF270334EB2D00D0A6FEC6C"},"recovery_id":0,"s":{"scalar":"20F90C540EE00133C911EA2A9ADE2ABBCC7AD820687F75E011DFEEC94DB10CD6"}}}' --accountId alexkushnir.testnet --gas 300000000000000 + +near call v1.signer-dev.testnet join '{"cipher_pk":[59,105,187,93,147,173,85,119,242,237,171,117,87,221,135,181,28,120,239,58,50,198,137,77,219,16,151,195,93,140,92,88],"sign_pk":"ed25519:J75xXmF7WUPS3xCm3hy2tgwLCKdYM1iJd4BWF8sWVnae","url":"http://localhost:3030"}' --accountId alexkushnir.testnet --gas 300000000000000 + +near call v1.signer-dev.testnet vote_join '{"candidate":"alexkushnir.testnet"}' --accountId alexkushnir.testnet --gas 300000000000000 + +near call v1.signer-dev.testnet vote_leave '{"kick":"alexkushnir.testnet"}' --accountId alexkushnir.testnet --gas 300000000000000 + +near call v1.signer-dev.testnet vote_pk '{"public_key": ed25519:J75xXmF7WUPS3xCm3hy2tgwLCKdYM1iJd4BWF8sWVnae}' --accountId alexkushnir.testnet --gas 300000000000000 + +near call v1.signer-dev.testnet vote_reshared '{"epoch": 1}' --accountId alexkushnir.testnet --gas 300000000000000 + +near call v1.signer-dev.testnet propose_update --base64 "AAHgkwQAAAAAAADdbQAAAAAAAgAAAEAAAAAABAAAAABAAMAnCQAAAAAAAAAAAAACAAAAACAAyK8AAAAAAAAAAAAAyK8AAAAAAABADQMAAAAAAABcJgUAAAAAAAAAAAAAAAAAAAAA" --accountId alexkushnir.testnet --gas 300000000000000 + +near call v1.signer-dev.testnet vote_update '{"id": 0}' --accountId alexkushnir.testnet --gas 300000000000000 + + +## Contract developer helper API + +near call v1.signer-dev.testnet init '{"candidates":{"candidates":{"alice.near":{"account_id":"alice.near","cipher_pk":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sign_pk":"ed25519:J75xXmF7WUPS3xCm3hy2tgwLCKdYM1iJd4BWF8sWVnae","url":"127.0.0.1"},"bob.near":{"account_id":"bob.near","cipher_pk":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sign_pk":"ed25519:J75xXmF7WUPS3xCm3hy2tgwLCKdYM1iJd4BWF8sWVnae","url":"127.0.0.1"},"caesar.near":{"account_id":"caesar.near","cipher_pk":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sign_pk":"ed25519:J75xXmF7WUPS3xCm3hy2tgwLCKdYM1iJd4BWF8sWVnae","url":"127.0.0.1"}}},"threshold":1}' --accountId alexkushnir.testnet --gas 300000000000000 + +near call v1.signer-dev.testnet init_running '{"epoch":0,"participants":{"account_to_participant_id":{"alice.near":0,"bob.near":1,"caesar.near":2},"next_id":3,"participants":{"alice.near":{"account_id":"alice.near","cipher_pk":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sign_pk":"ed25519:J75xXmF7WUPS3xCm3hy2tgwLCKdYM1iJd4BWF8sWVnae","url":"127.0.0.1"},"bob.near":{"account_id":"bob.near","cipher_pk":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sign_pk":"ed25519:J75xXmF7WUPS3xCm3hy2tgwLCKdYM1iJd4BWF8sWVnae","url":"127.0.0.1"},"caesar.near":{"account_id":"caesar.near","cipher_pk":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sign_pk":"ed25519:J75xXmF7WUPS3xCm3hy2tgwLCKdYM1iJd4BWF8sWVnae","url":"127.0.0.1"}}},"public_key":"ed25519:J75xXmF7WUPS3xCm3hy2tgwLCKdYM1iJd4BWF8sWVnae","threshold":2}' --accountId alexkushnir.testnet --gas 300000000000000 + +near view v1.signer-dev.testnet migrate + +near view v1.signer-dev.testnet state + +near view v1.signer-dev.testnet config + +near view v1.signer-dev.testnet version + diff --git a/integration-tests/chain-signatures/Cargo.lock b/integration-tests/chain-signatures/Cargo.lock index 621a9140..df129962 100644 --- a/integration-tests/chain-signatures/Cargo.lock +++ b/integration-tests/chain-signatures/Cargo.lock @@ -3339,6 +3339,7 @@ dependencies = [ "near-lake-framework", "near-lake-primitives", "near-primitives 0.26.0", + "near-sdk", "near-workspaces", "once_cell", "rand 0.7.3", diff --git a/integration-tests/chain-signatures/Cargo.toml b/integration-tests/chain-signatures/Cargo.toml index 1b80084b..f6eeb76e 100644 --- a/integration-tests/chain-signatures/Cargo.toml +++ b/integration-tests/chain-signatures/Cargo.toml @@ -28,6 +28,7 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } thiserror = "1" url = { version = "2.4.0", features = ["serde"] } +web3 = "0.19.0" deadpool-redis = "0.18.0" # crypto dependencies @@ -41,6 +42,7 @@ k256 = { version = "0.13.1", features = ["sha256", "ecdsa", "serde"] } near-account-id = "1" near-crypto = "0.26.0" near-fetch = "0.6.0" +near-sdk = "5.2.1" near-jsonrpc-client = "0.13.0" near-primitives = "0.26.0" near-lake-framework = { git = "https://github.com/near/near-lake-framework-rs", branch = "node/2.3.0" } @@ -60,7 +62,6 @@ test-log = { version = "0.2.12", features = ["log", "trace"] } # crypto dependencies ecdsa = "0.16.9" ethers-core = "2.0.13" -web3 = "0.19.0" secp256k1 = "0.28.2" [build-dependencies] diff --git a/integration-tests/chain-signatures/src/commands.rs b/integration-tests/chain-signatures/src/commands.rs new file mode 100644 index 00000000..0f09ceb5 --- /dev/null +++ b/integration-tests/chain-signatures/src/commands.rs @@ -0,0 +1,164 @@ +use std::str::FromStr; + +use crypto_shared::{ScalarExt, SerializableAffinePoint, SerializableScalar, SignatureResponse}; +use k256::Scalar; +use mpc_contract::{ + config::Config, + primitives::{CandidateInfo, Candidates, Participants, SignRequest, SignatureRequest}, + update::ProposeUpdateArgs, +}; +use mpc_keys::hpke; +use near_account_id::AccountId; +use near_primitives::borsh; +use near_sdk::PublicKey; +use serde_json::json; + +const PAYLOAD: [u8; 32] = [ + 12, 1, 2, 0, 4, 5, 6, 8, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 44, +]; + +const SIGN_PK: &str = "ed25519:J75xXmF7WUPS3xCm3hy2tgwLCKdYM1iJd4BWF8sWVnae"; + +pub fn sing_command(contract_id: &AccountId, caller_id: &AccountId) -> anyhow::Result { + let sign_request = SignRequest { + payload: PAYLOAD, + path: "test".into(), + key_version: 0, + }; + + let request_json = format!( + "'{}'", + serde_json::to_string(&json!({"request": sign_request}))? + ); + + Ok(format!( + "near call {} sign {} --accountId {} --gas 300000000000000 --deposit 1", + contract_id, request_json, caller_id + )) +} + +pub fn respond_command(contract_id: &AccountId, caller_id: &AccountId) -> anyhow::Result { + let payload_hashed = web3::signing::keccak256(&PAYLOAD); + + let request = SignatureRequest::new( + Scalar::from_bytes(payload_hashed) + .ok_or_else(|| anyhow::anyhow!("Failed to convert bytes to Scalar"))?, + caller_id, + "test", + ); + + 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 request_json = format!( + "'{}'", + serde_json::to_string(&json!({"request": request, "response": response})).unwrap() + ); + + Ok(format!( + "near call {} respond {} --accountId {} --gas 300000000000000", + contract_id, request_json, caller_id + )) +} + +pub fn join_command(contract_id: &AccountId, caller_id: &AccountId) -> anyhow::Result { + let url = "http://localhost:3030"; + let (_, cipher_pk) = hpke::generate(); + let sign_pk = PublicKey::from_str(SIGN_PK)?; + + let join_json = format!( + "'{}'", + serde_json::to_string(&json!({"url": url, "cipher_pk": cipher_pk, "sign_pk": sign_pk}))? + ); + + Ok(format!( + "near call {} join {} --accountId {} --gas 300000000000000", + contract_id, join_json, caller_id + )) +} + +pub fn proposed_updates_command( + contract_id: &AccountId, + caller_id: &AccountId, +) -> anyhow::Result { + let args = ProposeUpdateArgs { + code: None, + config: Some(Config::default()), + }; + + let borsh_args = borsh::to_vec(&args)?; + + let base64_encoded = near_primitives::serialize::to_base64(borsh_args.as_slice()); + + Ok(format!( + "near call {} propose_update --base64 {:?} --accountId {} --gas 300000000000000", + contract_id, base64_encoded, caller_id + )) +} + +pub fn init_command(contract_id: &AccountId, caller_id: &AccountId) -> anyhow::Result { + let threshold: usize = 1; + let candidates: Candidates = dummy_candidates(); + + let init_json = format!( + "'{}'", + serde_json::to_string(&json!({"threshold": threshold, "candidates": candidates}))? + ); + + Ok(format!( + "near call {} init {} --accountId {} --gas 300000000000000", + contract_id, init_json, caller_id + )) +} + +pub fn init_running_command( + contract_id: &AccountId, + caller_id: &AccountId, +) -> anyhow::Result { + let init_running_json = format!( + "'{}'", + serde_json::to_string( + &json!({"epoch": 0, "participants": Participants::from(dummy_candidates()), "threshold": 2,"public_key": PublicKey::from_str(SIGN_PK)? }) + )? + ); + + Ok(format!( + "near call {} init_running {} --accountId {} --gas 300000000000000", + contract_id, init_running_json, caller_id + )) +} + +pub fn dummy_candidates() -> Candidates { + let mut candidates = Candidates::new(); + let names: Vec = vec![ + "alice.near".parse().unwrap(), + "bob.near".parse().unwrap(), + "caesar.near".parse().unwrap(), + ]; + + for account_id in names { + candidates.insert( + account_id.clone(), + CandidateInfo { + account_id, + url: "127.0.0.1".into(), + cipher_pk: [0; 32], + sign_pk: PublicKey::from_str(SIGN_PK).unwrap(), + }, + ); + } + candidates +} diff --git a/integration-tests/chain-signatures/src/main.rs b/integration-tests/chain-signatures/src/main.rs index 65e1548f..37f01f91 100644 --- a/integration-tests/chain-signatures/src/main.rs +++ b/integration-tests/chain-signatures/src/main.rs @@ -1,9 +1,19 @@ +use std::fs::File; +use std::io::Write; +use std::str::FromStr; +use std::vec; + use clap::Parser; use integration_tests_chain_signatures::containers::DockerClient; use integration_tests_chain_signatures::{dry_run, run, utils, MultichainConfig}; +use near_account_id::AccountId; +use near_crypto::PublicKey; +use serde_json::json; use tokio::signal; use tracing_subscriber::EnvFilter; +mod commands; + #[derive(Parser, Debug)] enum Cli { /// Spin up dependent services and mpc nodes @@ -15,6 +25,8 @@ enum Cli { }, /// Spin up dependent services but not mpc nodes DepServices, + /// Generate example commands to interact with the contract + ContractCommands, } #[tokio::main] @@ -79,6 +91,98 @@ async fn main() -> anyhow::Result<()> { println!("Received Ctrl-C"); println!("Stopped dependency services"); } + Cli::ContractCommands => { + println!("Building a doc with example commands"); + let path = "../../chain-signatures/contract/EXAMPLE.md"; + let mut file = File::create(path)?; + let mut doc: Vec = vec![]; + let contract_account_id = AccountId::from_str("v1.signer-dev.testnet")?; + let caller_account_id = AccountId::from_str("caller.testnet")?; + let public_key: PublicKey = + "ed25519:J75xXmF7WUPS3xCm3hy2tgwLCKdYM1iJd4BWF8sWVnae".parse()?; + + doc.push( + "# Iteracting with contract using NEAR CLI\nAll data is fake and used for example purposes\nIt's necessary to update script after contract API changes\n## User contract API" + .to_string() + ); + + doc.push(commands::sing_command( + &contract_account_id, + &caller_account_id, + )?); + doc.push(format!("near view {} public_key", contract_account_id)); + + doc.push(format!( + "near view {} derived_public_key {}", + contract_account_id, + serde_json::to_string(&json!({"path": "test","predecessor": caller_account_id}))? + )); + + doc.push(format!( + "near view {} latest_key_version", + contract_account_id + )); + + doc.push(format!( + "near view {} experimental_signature_deposit", + contract_account_id + )); + + doc.push(format!( + "\n## Node API\n\n{}\n\n{}", + commands::respond_command(&contract_account_id, &caller_account_id,)?, + commands::join_command(&contract_account_id, &caller_account_id,)? + )); + + doc.push(format!( + "near call {} vote_join '{{\"candidate\":\"{}\"}}' --accountId {} --gas 300000000000000", + contract_account_id, caller_account_id, caller_account_id + )); + + doc.push(format!( + "near call {} vote_leave '{{\"kick\":\"{}\"}}' --accountId {} --gas 300000000000000", + contract_account_id, caller_account_id, caller_account_id + )); + + doc.push(format!( + "near call {} vote_pk '{{\"public_key\": {}}}' --accountId {} --gas 300000000000000", + contract_account_id, public_key, caller_account_id + )); + + doc.push(format!( + "near call {} vote_reshared '{{\"epoch\": 1}}' --accountId {} --gas 300000000000000", + contract_account_id, caller_account_id + )); + + doc.push(commands::proposed_updates_command( + &contract_account_id, + &caller_account_id, + )?); + + doc.push(format!( + "near call {} vote_update '{{\"id\": 0}}' --accountId {} --gas 300000000000000", + contract_account_id, caller_account_id + )); + + doc.push(format!( + "\n## Contract developer helper API\n\n{}\n\n{}", + commands::init_command(&contract_account_id, &caller_account_id,)?, + commands::init_running_command(&contract_account_id, &caller_account_id,)? + )); + + doc.push(format!("near view {} migrate", contract_account_id)); + + doc.push(format!("near view {} state", contract_account_id)); + + doc.push(format!("near view {} config", contract_account_id)); + + doc.push(format!("near view {} version", contract_account_id)); + + for arg in doc { + file.write_all(arg.as_bytes())?; + file.write_all("\n\n".as_bytes())?; + } + } } Ok(())