diff --git a/Cargo.toml b/Cargo.toml index 7af67d7..e11c9b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ libc = "*" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_with = "1.0" +serde_bytes = "0.11.2" time = "*" secp256k1 = { version = "0.15.0", features = ["rand", "serde"] } curve25519-dalek = { version = "1", features = ["serde"] } diff --git a/py/libbolt.py b/py/libbolt.py index a891458..4c2b8f1 100644 --- a/py/libbolt.py +++ b/py/libbolt.py @@ -63,19 +63,16 @@ class Libbolt(object): self.lib.ffishim_bidirectional_customer_close.argtypes = (c_void_p, c_void_p) self.lib.ffishim_bidirectional_customer_close.restype = c_void_p - self.lib.ffishim_bidirectional_merchant_close.argtypes = (c_void_p, c_void_p, c_void_p, c_void_p) + self.lib.ffishim_bidirectional_merchant_close.argtypes = (c_void_p, c_void_p, c_void_p, c_void_p, c_void_p) self.lib.ffishim_bidirectional_merchant_close.restype = c_void_p # ON-CHAIN BOLT LOGIC / WTPs - self.lib.ffishim_bidirectional_verify_open_channel.argtypes = (c_void_p, c_void_p, c_void_p, c_void_p) - self.lib.ffishim_bidirectional_verify_open_channel.restype = c_void_p + self.lib.ffishim_bidirectional_wtp_verify_cust_close_message.argtypes = (c_void_p, c_void_p, c_void_p, c_void_p) + self.lib.ffishim_bidirectional_wtp_verify_cust_close_message.restype = c_void_p - #self.lib.ffishim_bidirectional_verify_close_channel.argtypes = (c_void_p, c_void_p) - #self.lib.ffishim_bidirectional_verify_close_channel.restype = c_void_p - - # self.lib.ffishim_bidirectional_resolve.argtypes = (c_void_p, c_void_p, c_void_p, c_void_p, c_void_p) - # self.lib.ffishim_bidirectional_resolve.restype = c_void_p + self.lib.ffishim_bidirectional_wtp_verify_merch_close_message.argtypes = (c_void_p, c_void_p, c_void_p) + self.lib.ffishim_bidirectional_wtp_verify_merch_close_message.restype = c_void_p self.lib.ffishim_free_string.argtypes = (c_void_p, ) @@ -163,55 +160,40 @@ class Libbolt(object): output_dictionary = ast.literal_eval(ctypes.cast(output_string, ctypes.c_char_p).value.decode('utf-8')) return output_dictionary.get('cust_close') - def bidirectional_merchant_close(self, channel_state, channel_token, cust_close, merch_state): + def bidirectional_merchant_close(self, channel_state, channel_token, address, cust_close, merch_state): output_string = self.lib.ffishim_bidirectional_merchant_close(channel_state.encode(), channel_token.encode(), - cust_close.encode(), merch_state.encode()) + address.encode(), cust_close.encode(), merch_state.encode()) output_dictionary = ast.literal_eval(ctypes.cast(output_string, ctypes.c_char_p).value.decode('utf-8')) - return (output_dictionary.get('wpk'), output_dictionary.get('revoke_token'), output_dictionary.get('error')) + return (output_dictionary.get('wpk'), output_dictionary.get('merch_close'), output_dictionary.get('error')) # WTP logic def wtp_get_wallet(self, cust_state): - cust_state_dict = self.interperate_json_string_as_dictionary(cust_state) + cust_state_dict = self._interperate_json_string_as_dictionary(cust_state) return json.dumps(cust_state_dict.get("wpk")), json.dumps(cust_state_dict.get("wallet")) def wtp_get_close_token(self, cust_close): - cust_close_dict = self.interperate_json_string_as_dictionary(cust_close) + cust_close_dict = self._interperate_json_string_as_dictionary(cust_close) return json.dumps(cust_close_dict.get("signature")) - def wtp_verify_open_channel(self, channel_token, wpk, cust_close_wallet, close_token): - output_string = self.lib.ffishim_bidirectional_verify_open_channel(channel_token.encode(), wpk.encode(), cust_close_wallet.encode(), close_token.encode()) + def wtp_verify_cust_close_message(self, channel_token, wpk, cust_close_wallet, close_token): + output_string = self.lib.ffishim_bidirectional_wtp_verify_cust_close_message(channel_token.encode(), + wpk.encode(), + cust_close_wallet.encode(), + close_token.encode()) output_dictionary = ast.literal_eval(ctypes.cast(output_string, ctypes.c_char_p).value.decode('utf-8')) return output_dictionary.get('result') -# def bidirectional_resolve(self, pp, cust_data, merch_data, cust_closure, merch_closure): -# output_string = self.lib.ffishim_bidirectional_resolve( pp.encode(), cust_data.encode(), merch_data.encode(), cust_closure.encode(), merch_closure.encode()) -# output_dictionary = ast.literal_eval(ctypes.cast(output_string, ctypes.c_char_p).value.decode('utf-8')) -# return (int(output_dictionary['new_b0_cust']), int(output_dictionary['new_b0_merch'])) -# -# # -------------------------------------------- -# def commit_scheme_decommit(self, csp, commitment, x): -# output_string = self.lib.ffishim_commit_scheme_decommit(csp.encode(), commitment.encode(), x.encode()) -# output_dictionary = ast.literal_eval(ctypes.cast(output_string, ctypes.c_char_p).value.decode('utf-8')) -# if output_dictionary['return_value'] == 'true': -# return True -# return False + def wtp_verify_merch_close_message(self, channel_token, wpk, merch_close): + output_string = self.lib.ffishim_bidirectional_wtp_verify_merch_close_message(channel_token.encode(), + wpk.encode(), + merch_close.encode()) + output_dictionary = ast.literal_eval(ctypes.cast(output_string, ctypes.c_char_p).value.decode('utf-8')) + return output_dictionary.get('result') - def interperate_json_string_as_dictionary(self, json_string): + def _interperate_json_string_as_dictionary(self, json_string): return ast.literal_eval(json_string) - def util_convert_int_list_to_hex_string(self, dictionary): - return "".join(["{0:02x}".format(x) for x in dictionary]) - - # def util_extract_public_key_from_keypair(self, keypair): - # # Interperate the input keypair struct as a dictionary and then extract - # dictionary = self.interperate_json_string_as_dictionary(keypair) - # return json.dumps(dictionary['pk']) - # - # def util_extract_pub_bases_from_keypair(self, keypair): - # dictionary = self.interperate_json_string_as_dictionary(keypair) - # return json.dumps(dictionary['bases']) - if platform == 'darwin': prefix = 'lib' ext = 'dylib' @@ -285,6 +267,8 @@ def run_unit_test(): (cust_state, is_pay_valid) = libbolt.bidirectional_pay_verify_payment_token(channel_state, cust_state, pay_token) print("Pay token is valid: ", is_pay_valid) + old_cust_close = libbolt.bidirectional_customer_close(channel_state, cust_state) + # make a payment amount = 10 (payment_proof2, new_cust_state2) = libbolt.bidirectional_pay_generate_payment_proof(channel_state, cust_state, amount) @@ -312,8 +296,20 @@ def run_unit_test(): print("Cust close msg: ", cust_close) print("<========================================>") - merch_close_tokens = libbolt.bidirectional_merchant_close(channel_state, channel_token, cust_close, merch_state) - print("Merch close tokens: ", merch_close_tokens) + # normal case: no action b/c cust close is valid + address = "11" * 32 + merch_close = libbolt.bidirectional_merchant_close(channel_state, channel_token, address, cust_close, merch_state) + print("Customer initiated - Merch close msg: ", merch_close) + print("<========================================>") + + # common case: merchant catches customer double spending + address = "11" * 32 + merch_wpk, merch_close_msg, _ = libbolt.bidirectional_merchant_close(channel_state, channel_token, address, old_cust_close, merch_state) + print("Double spend - Merch close msg: ", merch_close_msg) + merch_close_valid = libbolt.wtp_verify_merch_close_message(channel_token, merch_wpk, merch_close_msg) + print("Merchant close msg valid: ", merch_close_valid) + print("<========================================>") + print("<========================================>") wpk, cust_close_wallet = libbolt.wtp_get_wallet(cust_state) @@ -321,9 +317,11 @@ def run_unit_test(): print("close-msg wallet = ", cust_close_wallet) cust_close_token = libbolt.wtp_get_close_token(cust_close) print("close token: ", cust_close_token) - print("Valid channel opening: ", libbolt.wtp_verify_open_channel(channel_token, wpk, cust_close_wallet, cust_close_token)) + print("Valid channel opening: ", libbolt.wtp_verify_cust_close_message(channel_token, wpk, cust_close_wallet, cust_close_token)) + # TODO: merch close when cust_close represents correct channel state - print("Invalid channel opening: ", libbolt.wtp_verify_open_channel(channel_token, revoked_wpk, cust_close_wallet, cust_close_token)) + print("Invalid channel opening: ", libbolt.wtp_verify_cust_close_message(channel_token, revoked_wpk, cust_close_wallet, cust_close_token)) + print("<========================================>") if __name__ == "__main__": run_unit_test() diff --git a/src/channels.rs b/src/channels.rs index 0ab57c5..6b54a6a 100644 --- a/src/channels.rs +++ b/src/channels.rs @@ -15,7 +15,7 @@ use pairing::bls12_381::{Bls12}; use ff::PrimeField; use cl::{BlindKeyPair, KeyPair, Signature, PublicParams, setup}; use ped92::{CSParams, Commitment, CSMultiParams}; -use util::{hash_pubkey_to_fr, convert_int_to_fr, hash_to_fr, RevokedMessage}; +use util::{hash_pubkey_to_fr, convert_int_to_fr, hash_to_fr, RevokedMessage, hash_to_slice}; use rand::Rng; use std::collections::HashMap; use std::fmt::Display; @@ -104,7 +104,7 @@ pub struct ChannelState { pub struct ChannelToken { pub pk_c: Option, // pk_c pub pk_m: secp256k1::PublicKey, // pk_m - pub cl_pk_m: cl::PublicKey, // PK_m + pub cl_pk_m: cl::PublicKey, // PK_m (used for verifying blind signatures) pub mpk: cl::PublicParams, // mpk for PK_m pub comParams: CSMultiParams, } @@ -239,7 +239,6 @@ impl CustomerState { let ct_db= HashMap::new(); let pt_db= HashMap::new(); - //println!("Customer wallet formed -> now returning the structure to the caller."); return CustomerState { name: name, pk_c: pk_c, @@ -290,7 +289,6 @@ impl CustomerState { let is_valid = pk.verify(&mpk, &close_wallet, &unblind_close_token); if is_valid { // record the unblinded close token - //cp.pub_params.keypair self.close_tokens.insert( self.index, unblind_close_token); } return is_valid; @@ -417,18 +415,47 @@ impl CustomerState { } +// pub name: String, +// pub pk_c: secp256k1::PublicKey, +// sk_c: secp256k1::SecretKey, +// pub cust_balance: i32, // +// pub merch_balance: i32, +// pub wpk: secp256k1::PublicKey, // keypair bound to the wallet +// wsk: secp256k1::SecretKey, +// old_kp: Option, // old wallet key pair +// t: E::Fr, // randomness used to form the commitment +// wallet: Wallet, // vector of field elements that represent wallet +// pub w_com: Commitment, // commitment to the current state of the wallet +// index: i32, +// close_tokens: HashMap>, +// pay_tokens: HashMap> + + impl fmt::Display for CustomerState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut content = String::new(); - content = format!("pk = {}\n", &self.pk_c); + content = format!("name = {}\n", &self.name); + content = format!("{}pk = {}\n", content, &self.pk_c); content = format!("{}sk = {}\n", content, &self.sk_c); content = format!("{}cust-bal = {}\n", content, &self.cust_balance); content = format!("{}merch-bal = {}\n", content, &self.merch_balance); - // add remaining fields + content = format!("{}wpk = {}\nwsk = {}\n", content, &self.wpk, &self.wsk); + if (!self.old_kp.is_none()) { + let old_kp = self.old_kp.unwrap(); + content = format!("{}revoked: wpk = {}\nrevoked: wsk = {}\n", content, &old_kp.wpk, &old_kp.wsk); + } + content = format!("{}t = {}\n", content, &self.t); + write!(f, "CustomerState : (\n{}\n)", &content) } } +#[derive(Clone, Serialize, Deserialize)] +pub struct ChannelcloseM { + pub address: String, + pub revoke: Option, + pub signature: secp256k1::Signature +} /// /// Merchant wallet (NEW) @@ -562,6 +589,19 @@ impl MerchantState { Err(BoltError::new("verify_revoke_token - Failed to verify the revoke token for wpk!")) } + pub fn sign_revoke_message(&self, address: String, revoke_token: &Option) -> ChannelcloseM { + let secp = secp256k1::Secp256k1::signing_only(); + let mut msg = Vec::new(); + msg.extend(address.as_bytes()); + if !revoke_token.is_none() { + let r = revoke_token.unwrap().serialize_der().to_vec(); + msg.extend(r); + } + let msg2 = secp256k1::Message::from_slice(&hash_to_slice(&msg) ).unwrap(); + let merch_sig = secp.sign(&msg2, &self.sk); + return ChannelcloseM { address: address.clone(), revoke: revoke_token.clone(), signature: merch_sig }; + } + } #[cfg(test)] @@ -612,10 +652,6 @@ mod tests { let amount = 10; let (pay_proof, new_com, old_wpk, new_cw) = cust_state.generate_payment(rng, &channel, amount); -// println!("{}", new_com); -// println!("wpk => {}", old_wpk); -// println!("{}", new_cw); - // new pay_token is not sent until revoke_token is obtained from the customer let new_close_token = merch_state.verify_payment(rng, &channel, &pay_proof, &new_com, &old_wpk, amount).unwrap(); diff --git a/src/ffishim.rs b/src/ffishim.rs index f422e70..e63151a 100644 --- a/src/ffishim.rs +++ b/src/ffishim.rs @@ -11,6 +11,7 @@ pub mod ffishim { use libc::c_char; use std::ffi::{CStr, CString}; use std::str; + use channels::ChannelcloseM; fn error_message(s: String) -> *mut c_char { let ser = ["{\'error\':\'", serde_json::to_string(&s).unwrap().as_str(), "\'}"].concat(); @@ -324,8 +325,11 @@ pub mod ffishim { } #[no_mangle] - pub extern fn ffishim_bidirectional_merchant_close(ser_channel_state: *mut c_char, ser_channel_token: *mut c_char, - ser_cust_close: *mut c_char, ser_merch_state: *mut c_char) -> *mut c_char { + pub extern fn ffishim_bidirectional_merchant_close(ser_channel_state: *mut c_char, + ser_channel_token: *mut c_char, + ser_address: *const c_char, + ser_cust_close: *mut c_char, + ser_merch_state: *mut c_char) -> *mut c_char { // Deserialize the channel state let channel_state: bidirectional::ChannelState = deserialize_object(ser_channel_state); // Deserialize the channel token @@ -335,20 +339,26 @@ pub mod ffishim { // Deserialize the merch wallet let merch_state: bidirectional::MerchantState = deserialize_object(ser_merch_state); + // Deserialize the destination address as a string + let ser_addr_bytes = unsafe { CStr::from_ptr(ser_address).to_bytes() }; + let address: &str = str::from_utf8(ser_addr_bytes).unwrap(); // make sure the bytes are UTF-8 + let option = bidirectional::merchant_close(&channel_state, &channel_token, &cust_close, &merch_state); let keys = match option { Ok(n) => n.unwrap(), Err(err) => return error_message(err), }; + let merch_close: bidirectional::ChannelcloseM = merch_state.sign_revoke_message(address.to_string(), &keys.revoke_token); + let ser = ["{\'wpk\':\'", serde_json::to_string(&keys.wpk).unwrap().as_str(), - "\', \'revoke_token\':\'", serde_json::to_string(&keys.revoke_token).unwrap().as_str(), "\'}"].concat(); + "\', \'merch_close\':\'", serde_json::to_string(&merch_close).unwrap().as_str(), "\'}"].concat(); let cser = CString::new(ser).unwrap(); cser.into_raw() } #[no_mangle] - pub extern fn ffishim_bidirectional_verify_open_channel(ser_channel_token: *mut c_char, + pub extern fn ffishim_bidirectional_wtp_verify_cust_close_message(ser_channel_token: *mut c_char, ser_wpk: *mut c_char, ser_close_msg: *mut c_char, ser_close_token: *mut c_char) -> *mut c_char { @@ -362,7 +372,26 @@ pub mod ffishim { // Deserialize the close token let close_token: bidirectional::Signature = deserialize_object(ser_close_token); // check the signatures - let token_valid = bidirectional::verify_open_channel(&channel_token, &wpk, &close_msg, &close_token); + let token_valid = bidirectional::wtp_verify_cust_close_message(&channel_token, &wpk, &close_msg, &close_token); + let ser = ["{\'result\':\'", serde_json::to_string(&token_valid).unwrap().as_str(), "\'}"].concat(); + let cser = CString::new(ser).unwrap(); + cser.into_raw() + } + + #[no_mangle] + pub extern fn ffishim_bidirectional_wtp_verify_merch_close_message(ser_channel_token: *mut c_char, ser_wpk: *mut c_char, ser_merch_close: *mut c_char) -> *mut c_char { + // Deserialize the channel token + let channel_token: bidirectional::ChannelToken = deserialize_object(ser_channel_token); + // Deserialize the wpk + let wpk: secp256k1::PublicKey = deserialize_object(ser_wpk); + // Deserialize the merch close + //let revoke_token: secp256k1::Signature = deserialize_object(ser_revoke_token); + let merch_close: bidirectional::ChannelcloseM = deserialize_object(ser_merch_close); + + let revoke_token_valid = bidirectional::wtp_verify_revoke_message(&channel_token, &wpk, &merch_close.revoke.unwrap()); + let merch_close_valid = bidirectional::wtp_verify_merch_close_message(&channel_token, &merch_close); + let token_valid = revoke_token_valid && merch_close_valid; + let ser = ["{\'result\':\'", serde_json::to_string(&token_valid).unwrap().as_str(), "\'}"].concat(); let cser = CString::new(ser).unwrap(); cser.into_raw() diff --git a/src/lib.rs b/src/lib.rs index 5b46040..3992bb3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,11 +124,12 @@ pub mod bidirectional { use serde::{Serialize, Deserialize}; use std::sync::mpsc::channel; - use util::RevokedMessage; + use util::{RevokedMessage, hash_to_slice}; pub use ped92::Commitment; pub use cl::{PublicKey, Signature}; pub use BoltResult; - pub use channels::{ChannelState, ChannelToken, CustomerState, MerchantState, PubKeyMap, ChannelParams, BoltError, ResultBoltSig}; + pub use channels::{ChannelState, ChannelToken, CustomerState, MerchantState, ChannelcloseM, + PubKeyMap, ChannelParams, BoltError, ResultBoltSig}; pub use nizk::{CommitmentProof, Proof}; pub use wallet::Wallet; pub use cl::PublicParams; @@ -430,6 +431,7 @@ pub mod bidirectional { let msg = secp256k1::Message::from_slice(&revoke_msg.hash_to_slice()).unwrap(); // verify that the revocation token is valid if secp.verify(&msg, &revoke_token, &wpk).is_ok() { + // compute signature on return Ok(Some(revoked_state.clone())); } } @@ -440,10 +442,14 @@ pub mod bidirectional { Err(String::from("merchant_close - Customer close message not valid!")) } +// pub fn wtp_sign_merch_close_message(address: &Vec, revoke_token: &Option, merch_state: &MerchantState) -> ChannelcloseM { +// return merch_state.sign_revoke_message(&address, &revoke_token); +// } + /// - /// WTP for validating that a close_token was well-formed + /// Used in open-channel WTP for validating that a close_token is a valid signature under < /// - pub fn verify_open_channel(channel_token: &ChannelToken, wpk: &secp256k1::PublicKey, close_msg: &wallet::Wallet, close_token: &Signature) -> bool { + pub fn wtp_verify_cust_close_message(channel_token: &ChannelToken, wpk: &secp256k1::PublicKey, close_msg: &wallet::Wallet, close_token: &Signature) -> bool { // close_msg => || || || || CLOSE // close_token = regular CL signature on close_msg // channel_token => @@ -459,7 +465,33 @@ pub mod bidirectional { return pkc_thesame && wpk_thesame && channel_token.cl_pk_m.verify(&channel_token.mpk, &close_msg.as_fr_vec(), &close_token); } + /// + /// Used in merch-close WTP for validating that revoke_token is a valid signature under and the message + /// + pub fn wtp_verify_revoke_message(channel_token: &ChannelToken, wpk: &secp256k1::PublicKey, revoke_token: &secp256k1::Signature) -> bool { + let secp = secp256k1::Secp256k1::verification_only(); + let revoke_msg = RevokedMessage::new(String::from("revoked"), wpk.clone()); + let msg = secp256k1::Message::from_slice(&revoke_msg.hash_to_slice()).unwrap(); + // verify that the revocation token is valid with respect to revoked || wpk + return secp.verify(&msg, &revoke_token, &wpk).is_ok(); + } + /// + /// Used in merch-close WTP for validating that merch_sig is a valid signature under on message + /// + pub fn wtp_verify_merch_close_message(channel_token: &ChannelToken, merch_close: &ChannelcloseM) -> bool { + let secp = secp256k1::Secp256k1::verification_only(); + let mut msg = Vec::new(); + msg.extend(merch_close.address.as_bytes()); + if !merch_close.revoke.is_none() { + // serialize signature in DER format + let r = merch_close.revoke.unwrap().serialize_der().to_vec(); + msg.extend(r); + } + let msg2 = secp256k1::Message::from_slice(&hash_to_slice(&msg)).unwrap(); + // verify that merch sig is valid with respect to dest_address + return secp.verify(&msg2, &merch_close.signature, &channel_token.pk_m).is_ok(); + } } #[cfg(all(test, feature = "unstable"))] diff --git a/src/util.rs b/src/util.rs index f73f06b..d7a9d3b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -114,6 +114,14 @@ pub fn hash_buffer_to_fr<'a, E: Engine>(prefix: &'a str, buf: &[u8; 64]) -> E::F return result.unwrap(); } +pub fn hash_to_slice(input_buf: &Vec) -> [u8; 32] { + let sha2_digest = sha512::hash(input_buf.as_slice()); + let mut hash_buf: [u8; 32] = [0; 32]; + hash_buf.copy_from_slice(&sha2_digest[0..32]); + return hash_buf; +} + + #[derive(Clone, Serialize, Deserialize)] pub struct RevokedMessage {