Skip to content

Commit

Permalink
feat: create generate signature csv by simple signer server
Browse files Browse the repository at this point in the history
  • Loading branch information
sifnoc committed Sep 14, 2023
1 parent bbf4363 commit bcea57b
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 0 deletions.
4 changes: 4 additions & 0 deletions backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ bincode = "1.3.3"

[build-dependencies]
ethers = { version = "2.0.7", default-features = false, features = ["ethers-solc"] }

[[example]]
name = "generate_signatures"
path = "examples/generate_signatures/main.rs"
87 changes: 87 additions & 0 deletions backend/examples/generate_signatures/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use std::{
error::Error,
fs::File,
io::{Read, Write},
net::{TcpListener, TcpStream},
str::FromStr,
thread::{sleep, spawn},
};

use csv::WriterBuilder;
use ethers::{
abi::{encode, Token},
prelude::SignerMiddleware,
signers::{LocalWallet, Signer, WalletError},
types::Signature,
utils::{keccak256, to_checksum},
};
use serde_json::{from_str, to_string_pretty};

mod remote_signer;
use remote_signer::start_server;
use summa_backend::apis::csv_parser::SignatureRecord;

// We provide simple request function for getting signatures from the signer server
fn send_request(message: &str) -> Result<String, std::io::Error> {
let mut stream = TcpStream::connect("127.0.0.1:8080")?;

let request = format!(
"POST /sign HTTP/1.1\r\nContent-Length: {}\r\n\r\n{}",
message.len(),
message
);

stream.write_all(request.as_bytes())?;

let mut response = String::new();
stream.read_to_string(&mut response)?;

Ok(response)
}

fn parse_response(response: &str) -> Result<Vec<SignatureRecord>, Box<dyn std::error::Error>> {
// Split the response into HTTP headers and body
let parts: Vec<&str> = response.split("\r\n\r\n").collect();

if parts.len() != 2 {
return Err("Invalid response format".into());
}

let json_str = parts[1];

// Parse the JSON response into a vector of SignatureRecord
let signatures: Vec<SignatureRecord> = serde_json::from_str(json_str)?;

Ok(signatures)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Let's assume the CEX has multiple private keys for generating message signatures for AddressOwnershipProof
// Start the server in a separate thread
spawn(|| start_server());

// Give the server a little time to start
sleep(std::time::Duration::from_secs(1));

// Given message to sign
// Note that, the message length are fixed for the server.
let message = "Summa proof of solvency for CryptoExchange";
let path = "src/apis/csv/signatures.csv";

// Request signatures with `message` to the signer server
let response = send_request(message)?;
let signatures = parse_response(&response)?;

// Write the signatures to a CSV file
let file = File::create(path)?;
let mut wtr = WriterBuilder::new().delimiter(b';').from_writer(file);

for signature in signatures {
wtr.serialize(signature)?;
}

wtr.flush()?; // This will ensure all bytes are written

Ok(())
}
98 changes: 98 additions & 0 deletions backend/examples/generate_signatures/remote_signer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use std::{
error::Error,
fs::File,
io::{Read, Write},
net::{TcpListener, TcpStream},
str::FromStr,
thread::{sleep, spawn},
};

use csv::WriterBuilder;
use ethers::{
abi::{encode, Token},
prelude::SignerMiddleware,
signers::{LocalWallet, Signer, WalletError},
types::Signature,
utils::{keccak256, to_checksum},
};
use serde_json::{from_str, to_string_pretty};

use summa_backend::apis::csv_parser::SignatureRecord;

async fn remote_signer(mut stream: TcpStream) {
let mut buffer = [0; 85];
let bytes_read = stream.read(&mut buffer).unwrap();

// This is insecure way to create wallet instances
// TODO: suggest better secure way to generate wallet instances
let private_keys = &[
"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
"0xde9be858da4a475276426320d5e9262ecfc3ba460bfac56360bfa6c4c28b4ee0",
];

let signing_wallets: Vec<LocalWallet> = private_keys
.iter()
.map(|private_key| LocalWallet::from_str(private_key).unwrap())
.collect();

let request = String::from_utf8_lossy(&buffer[..]);
if request.starts_with("POST /sign") {
// Extract the message from the request body
let message = request.split("\r\n\r\n").nth(1).unwrap_or("");

let encoded_message = encode(&[Token::String(message.to_owned())]);
let hashed_message = keccak256(encoded_message);

let mut signatures: Vec<SignatureRecord> = Vec::new();

// Iterating signing wallets and generate signatures to put `signatures` vector
for wallet in signing_wallets {
let signature = wallet.sign_message(hashed_message).await.unwrap();
let record = SignatureRecord::new(
"ETH".to_string(),
to_checksum(&wallet.address(), None), //
format!("0x{}", signature.to_string()),
message.to_string(),
);
signatures.push(record);
}

let json_response = to_string_pretty(&signatures).unwrap();

let response = format!(
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",
json_response.len(),
json_response
);

stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
} else {
let response = "HTTP/1.1 404 NOT FOUND\r\n\r\n";
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
}

fn handle_client(stream: TcpStream) {
let mut rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(remote_signer(stream));
}

pub fn start_server() {
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
println!("Example Signer server started on 127.0.0.1:8080");

for stream in listener.incoming() {
match stream {
Ok(stream) => {
std::thread::spawn(|| {
handle_client(stream);
});
}
Err(e) => {
eprintln!("Connection failed: {}", e);
}
}
}
}

0 comments on commit bcea57b

Please sign in to comment.