//! This crate is an experimental implementation of Blind Off-chain //! lightweight transactions (BOLT). //! //! It builds on academic work done by Ian Miers and Matthew Green - //! https://eprint.iacr.org/2016/701. //! //! Libbolt relies on BLS12-381 curves at 128-bit security, as implemented in a fork of //! [`pairing module`](https://github.com/boltlabs-inc/pairing). //! #![allow(non_snake_case)] #![allow(non_camel_case_types)] #![allow(unused_parens)] #![allow(non_upper_case_globals)] #![allow(unused_results)] #![allow(missing_docs)] #![cfg_attr(all(test, feature = "unstable"), feature(test))] #[cfg(all(test, feature = "unstable"))] extern crate test; extern crate ff_bl as ff; extern crate pairing_bl as pairing; extern crate rand; // extern crate rand_core; extern crate secp256k1; extern crate time; extern crate sha2; extern crate serde; extern crate serde_with; extern crate libc; extern crate hex; #[cfg(test)] extern crate core; extern crate serde_json; pub mod cl; pub mod ccs08; pub mod ped92; pub mod channels; pub mod nizk; pub mod util; pub mod wallet; pub mod ffishim; pub mod ffishim_bn256; use std::fmt; use std::str; use std::collections::HashMap; use ff::{Rand, Field}; use serde::{Serialize, Deserialize}; ////////////////////////////////// Utilities ////////////////////////////////// struct HexSlice<'a>(&'a [u8]); impl<'a> HexSlice<'a> { fn new(data: &'a T) -> HexSlice<'a> where T: ?Sized + AsRef<[u8]> + 'a { HexSlice(data.as_ref()) } } impl<'a> fmt::LowerHex for HexSlice<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for byte in self.0 { // Decide if you want upper- or lowercase results, // padding the values to two characters, spaces // between bytes, etc. write!(f, "{:x}", byte)?; } Ok(()) } } impl<'a> fmt::UpperHex for HexSlice<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for byte in self.0 { // Decide if you want upper- or lowercase results, // padding the values to two characters, spaces // between bytes, etc. write!(f, "{:X}", byte)?; } Ok(()) } } pub type BoltResult = Result, String>; #[macro_export] macro_rules! handle_bolt_result { ($e:expr) => (match $e { Ok(val) => val, Err(_) => None, }); } ////////////////////////////////// Utilities ////////////////////////////////// /////////////////////////////// Bidirectional //////////////////////////////// pub mod bidirectional { use rand::Rng; use util; use wallet; use pairing::Engine; use cl; // for blind signature use secp256k1; // for on-chain keys use HashMap; use serde::{Serialize, Deserialize}; 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, ChannelcloseM, PubKeyMap, ChannelParams, BoltError, ResultBoltType}; pub use nizk::NIZKProof; pub use wallet::Wallet; pub use cl::PublicParams; pub use ped92::CommitmentProof; #[derive(Clone, Serialize, Deserialize)] #[serde(bound(serialize = "::Fr: serde::Serialize, \ ::G1: serde::Serialize"))] #[serde(bound(deserialize = "::Fr: serde::Deserialize<'de>, \ ::G1: serde::Deserialize<'de>"))] pub struct ChannelcloseC { pub wpk: secp256k1::PublicKey, pub message: wallet::Wallet, pub signature: cl::Signature, } #[derive(Clone, Serialize, Deserialize)] #[serde(bound(serialize = "::Fr: serde::Serialize, \ ::G1: serde::Serialize, \ ::G2: serde::Serialize, \ ::Fqk: serde::Serialize" ))] #[serde(bound(deserialize = "::Fr: serde::Deserialize<'de>, \ ::G1: serde::Deserialize<'de>, \ ::G2: serde::Deserialize<'de>,\ ::Fqk: serde::Deserialize<'de>" ))] pub struct Payment { proof: NIZKProof, com: Commitment, wpk: secp256k1::PublicKey, amount: i64, } #[derive(Clone, Serialize, Deserialize)] pub struct RevokeToken { message: util::RevokedMessage, pub signature: secp256k1::Signature, } /// /// init_merchant - takes as input the public params, merchant balance and keypair. /// Generates merchant data which consists of channel token and merchant state. /// pub fn init_merchant<'a, R: Rng, E: Engine>(csprng: &mut R, channel_state: &mut ChannelState, name: &'a str) -> (ChannelToken, MerchantState, ChannelState) { // create new merchant state let merch_name = String::from(name); let (mut merch_state, mut channel_state) = MerchantState::::new(csprng, channel_state, merch_name); // initialize the merchant state let channel_token = merch_state.init(&mut channel_state); return (channel_token, merch_state, channel_state.clone()); } /// /// init_customer - takes as input the public params, channel state, commitment params, keypair, /// and initial balance for customer and merchant. Generate initial customer channel token, /// and wallet commitment. /// pub fn init_customer<'a, R: Rng, E: Engine>(csprng: &mut R, channel_token: &mut ChannelToken, b0_cust: i64, b0_merch: i64, name: &'a str) -> CustomerState where ::G1: serde::Serialize, ::G2: serde::Serialize, ::Fr: serde::Serialize { assert!(b0_cust >= 0); assert!(b0_merch >= 0); let cust_name = String::from(name); return CustomerState::::new(csprng, channel_token, b0_cust, b0_merch, cust_name); } /// /// establish_customer_generate_proof (Phase 1) - takes as input the public params, customer state and /// common public bases from merchant. Generates a PoK of the committed values in the /// new wallet. /// pub fn establish_customer_generate_proof(csprng: &mut R, channel_token: &ChannelToken, cust_state: &CustomerState) -> (Commitment, CommitmentProof) { let cust_com_proof = cust_state.generate_proof(csprng, channel_token); return (cust_state.w_com.clone(), cust_com_proof); } /// /// establish_merchant_issue_close_token (Phase 1) - takes as input the channel state, /// PoK of committed values from the customer. Generates close token (a blinded /// signature) over the contents of the customer's wallet. /// pub fn establish_merchant_issue_close_token(csprng: &mut R, channel_state: &ChannelState, com: &Commitment, com_proof: &CommitmentProof, channel_id: &E::Fr, init_cust_balance: i64, init_merch_balance: i64, merch_state: &MerchantState) -> BoltResult> { // verifies proof of committed values and derives blind signature on the committed values to the customer's initial wallet match merch_state.verify_proof(csprng, channel_state, com, com_proof, channel_id, init_cust_balance, init_merch_balance) { Ok(n) => Ok(Some(n.0)), // just close token Err(err) => Err(String::from(err.to_string())) } } /// /// establish_merchant_issue_pay_token (Phase 1) - takes as input the channel state, /// the commitment from the customer. Generates close token (a blinded /// signature) over the contents of the customer's wallet. /// pub fn establish_merchant_issue_pay_token(csprng: &mut R, channel_state: &ChannelState, com: &Commitment, merch_state: &MerchantState) -> cl::Signature { let cp = channel_state.cp.as_ref().unwrap(); let pay_token = merch_state.issue_pay_token(csprng, cp, com, false); return pay_token; } /// /// establish_customer_final - takes as input the channel state, customer state, /// and pay token (blinded sig) obtained from merchant. Add the returned /// blinded signature to the wallet. /// pub fn establish_customer_final(channel_state: &mut ChannelState, cust_state: &mut CustomerState, pay_token: &cl::Signature) -> bool { // verify the pay-token first if !cust_state.verify_pay_token(&channel_state, pay_token) { println!("establish_customer_final - Failed to verify the pay-token"); return false; } // only if both tokens have been stored if (cust_state.has_tokens()) { // must be an old wallet channel_state.channel_established = true; } return channel_state.channel_established; } ///// end of establish channel protocol /// /// generate_payment_proof (phase 1) - takes as input the public params, channel state, channel token, /// merchant public keys, old wallet and balance increment. Generate a new wallet commitment /// PoK of the committed values in new wallet and PoK of old wallet. Return new channel token, /// new wallet (minus blind signature and refund token) and payment proof. /// pub fn generate_payment_proof(csprng: &mut R, channel_state: &ChannelState, cust_state: &CustomerState, amount: i64) -> (Payment, CustomerState) { let tx_fee = channel_state.get_channel_fee(); let payment_amount = match tx_fee > 0 { true => amount + tx_fee, false => amount }; let (proof, com, wpk, new_cust_state) = cust_state.generate_payment(csprng, &channel_state, payment_amount); let payment = Payment { proof, com, wpk, amount }; return (payment, new_cust_state); } /// /// verify_payment (phase 1) - takes as input the public params, channel state, payment proof /// and merchant keys. If proof is valid, then merchant returns the refund token /// (i.e., partially blind signature on IOU with updated balance) /// pub fn verify_payment_proof(csprng: &mut R, channel_state: &ChannelState, payment: &Payment, merch_state: &mut MerchantState) -> cl::Signature { // if payment proof verifies, then returns close-token and records wpk => pay-token // if valid revoke_token is provided later for wpk, then release pay-token let tx_fee = channel_state.get_channel_fee(); let payment_amount = match tx_fee > 0 { true => payment.amount + tx_fee, false => payment.amount }; let new_close_token = merch_state.verify_payment(csprng, &channel_state, &payment.proof, &payment.com, &payment.wpk, payment_amount).unwrap(); // store the wpk since it has been revealed update_merchant_state(&mut merch_state.keys, &payment.wpk, None); return new_close_token; } /// /// Verify third party payment proof from two bi-directional channel payments with intermediary (payment amount /// pub fn verify_multiple_payment_proofs(csprng: &mut R, channel_state: &ChannelState, sender_payment: &Payment, receiver_payment: &Payment, merch_state: &mut MerchantState) -> BoltResult<(cl::Signature, cl::Signature)> { let tx_fee = channel_state.get_channel_fee(); let amount = sender_payment.amount + receiver_payment.amount; if amount != 0 { // we want to check this relation in ZK without knowing the amount return Err(String::from("payments do not offset")); } let new_close_token = merch_state.verify_payment(csprng, &channel_state, &sender_payment.proof, &sender_payment.com, &sender_payment.wpk, sender_payment.amount + tx_fee).unwrap(); let cond_close_token = merch_state.verify_payment(csprng, &channel_state, &receiver_payment.proof, &receiver_payment.com, &receiver_payment.wpk, receiver_payment.amount + tx_fee).unwrap(); // store the wpk since it has been revealed update_merchant_state(&mut merch_state.keys, &sender_payment.wpk, None); update_merchant_state(&mut merch_state.keys, &receiver_payment.wpk, None); return Ok(Some((new_close_token, cond_close_token))); } /// /// generate_revoke_token (phase 2) - takes as input the public params, old wallet, new wallet, /// merchant's verification key and refund token. If the refund token is valid, generate /// a revocation token for the old wallet public key. /// pub fn generate_revoke_token(channel_state: &ChannelState, old_cust_state: &mut CustomerState, new_cust_state: CustomerState, new_close_token: &cl::Signature) -> RevokeToken { // let's update the old wallet assert!(old_cust_state.update(new_cust_state)); // generate the token after verifying that the close token is valid let (message, signature) = old_cust_state.generate_revoke_token(channel_state, new_close_token).unwrap(); // return the revoke token (msg + sig pair) return RevokeToken { message, signature }; } /// /// verify_revoke_token (phase 2) - takes as input revoke message and signature /// from the customer and the merchant state. If the revocation token is valid, /// generate a new signature for the new wallet (from the PoK of committed values in new wallet). /// pub fn verify_revoke_token(rt: &RevokeToken, merch_state: &mut MerchantState) -> BoltResult> { let pay_token_result = merch_state.verify_revoke_token(&rt.signature, &rt.message, &rt.message.wpk); let new_pay_token = match pay_token_result { Ok(n) => n, Err(err) => return Err(String::from(err.to_string())) }; update_merchant_state(&mut merch_state.keys, &rt.message.wpk, Some(rt.signature.clone())); Ok(Some(new_pay_token)) } /// /// verify_multiple_revoke_tokens (phase 2) - takes as input revoke messages and signatures /// from the sender and receiver and the merchant state of the intermediary. /// If the revocation tokens are valid, generate new signatures for the new wallets of both /// sender and receiver (from the PoK of committed values in new wallet). /// pub fn verify_multiple_revoke_tokens(rt_sender: &RevokeToken, rt_receiver: &RevokeToken, merch_state: &mut MerchantState) -> BoltResult<(cl::Signature, cl::Signature)> { let pay_token_sender_result = merch_state.verify_revoke_token(&rt_sender.signature, &rt_sender.message, &rt_sender.message.wpk); let pay_token_receiver_result = merch_state.verify_revoke_token(&rt_receiver.signature, &rt_receiver.message, &rt_receiver.message.wpk); let new_pay_token_sender = match pay_token_sender_result { Ok(n) => n, Err(err) => return Err(String::from(err.to_string())) }; let new_pay_token_receiver = match pay_token_receiver_result { Ok(n) => n, Err(err) => return Err(String::from(err.to_string())) }; update_merchant_state(&mut merch_state.keys, &rt_sender.message.wpk, Some(rt_sender.signature.clone())); update_merchant_state(&mut merch_state.keys, &rt_receiver.message.wpk, Some(rt_receiver.signature.clone())); Ok(Some((new_pay_token_sender, new_pay_token_receiver))) } ///// end of pay protocol // for customer => on input a wallet w, it outputs a customer channel closure message /// /// customer_close - takes as input the channel state, merchant's verification /// key, and customer state. Generates a channel closure message for customer. /// pub fn customer_close(channel_state: &ChannelState, cust_state: &CustomerState) -> ChannelcloseC { if !channel_state.channel_established { panic!("Cannot close a channel that has not been established!"); } let mut wallet = cust_state.get_wallet(); let close_token = cust_state.get_close_token(); let cp = channel_state.cp.as_ref().unwrap(); let pk = cp.pub_params.pk.get_pub_key(); let close_wallet = wallet.with_close(String::from("close")); assert!(pk.verify(&cp.pub_params.mpk, &close_wallet, &close_token)); ChannelcloseC { wpk: cust_state.wpk, message: wallet, signature: close_token } } fn update_merchant_state(db: &mut HashMap, wpk: &secp256k1::PublicKey, rev: Option) { let fingerprint = util::compute_pub_key_fingerprint(wpk); //println!("Print fingerprint: {}", fingerprint); if !rev.is_none() { let cust_pub_key = PubKeyMap { wpk: wpk.clone(), revoke_token: Some(rev.unwrap().clone()) }; db.insert(fingerprint, cust_pub_key); } else { let cust_pub_key = PubKeyMap { wpk: wpk.clone(), revoke_token: None }; db.insert(fingerprint, cust_pub_key); } } /// /// merchant_close - takes as input the channel state, channel token, customer close msg/sig, /// Returns tokens for merchant close transaction (only if customer close message is found to be a /// double spend). If not, then None is returned. /// pub fn merchant_close(channel_state: &ChannelState, channel_token: &ChannelToken, cust_close: &ChannelcloseC, merch_state: &MerchantState) -> BoltResult { if (!channel_state.channel_established) { return Err(String::from("merchant_close - Channel not established! Cannot generate channel closure message.")); } let cp = channel_state.cp.as_ref().unwrap(); let pk = cp.pub_params.pk.get_pub_key(); let mut wallet = cust_close.message.clone(); let close_wallet = wallet.with_close(String::from("close")).clone(); let close_token = cust_close.signature.clone(); let is_valid = pk.verify(&channel_token.mpk, &close_wallet, &close_token); if is_valid { let wpk = cust_close.wpk; // found the wpk, which means old close token let fingerprint = util::compute_pub_key_fingerprint(&wpk); if merch_state.keys.contains_key(&fingerprint) { let revoked_state = merch_state.keys.get(&fingerprint).unwrap(); if !revoked_state.revoke_token.is_none() { let revoke_token = revoked_state.revoke_token.unwrap().clone(); // verify the revoked state first before returning let secp = secp256k1::Secp256k1::new(); 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 if secp.verify(&msg, &revoke_token, &wpk).is_ok() { // compute signature on return Ok(Some(revoked_state.clone())); } } return Err(String::from("merchant_close - Found wpk but could not find the revoke token. Merchant abort detected.")); } return Err(String::from("merchant_close - Could not find entry for wpk & revoke token pair. Valid close!")); } Err(String::from("merchant_close - Customer close message not valid!")) } /// /// Used in open-channel WTP for validating that a close_token is a valid signature under < /// 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 => // (1) check that channel token and close msg are consistent (e.g., close_msg.pk_c == H(channel_token.pk_c) && let pk_c = channel_token.pk_c.unwrap(); let chan_token_pk_c = util::hash_pubkey_to_fr::(&pk_c); let chan_token_wpk = util::hash_pubkey_to_fr::(&wpk); let pkc_thesame = (close_msg.channelId == chan_token_pk_c); // (2) check that wpk matches what's in the close msg let wpk_thesame = (close_msg.wpk == chan_token_wpk); 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(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(); } } pub mod wtp_utils { // Useful routines that simplify the Bolt WTP implementation for Zcash use pairing::bls12_381::Bls12; use ::{util, BoltResult}; use cl; use ped92::CSMultiParams; pub use cl::Signature; pub use channels::ChannelToken; pub use wallet::Wallet; use channels::ChannelcloseM; const BLS12_381_CHANNEL_TOKEN_LEN: usize = 1074; const BLS12_381_G1_LEN: usize = 48; const BLS12_381_G2_LEN: usize = 96; const SECP256K1_PK_LEN: usize = 33; const ADDRESS_LEN: usize = 33; pub fn reconstruct_secp_public_key(pk_bytes: &[u8; SECP256K1_PK_LEN]) -> secp256k1::PublicKey { return secp256k1::PublicKey::from_slice(pk_bytes).unwrap(); } pub fn reconstruct_secp_signature(sig_bytes: &[u8]) -> secp256k1::Signature { return secp256k1::Signature::from_der(sig_bytes).unwrap(); } pub fn reconstruct_close_wallet_bls12(channel_token: &ChannelToken, wpk: &secp256k1::PublicKey, cust_bal: u32, merch_bal: u32) -> Wallet { let channelId = channel_token.compute_channel_id(); let wpk_h = util::hash_pubkey_to_fr::(&wpk); let close = util::hash_to_fr::(String::from("close").into_bytes()); return Wallet { channelId, wpk: wpk_h, bc: cust_bal as i64, bm: merch_bal as i64, close: Some(close) } } pub fn reconstruct_signature_bls12(sig: &Vec) -> BoltResult> { if (sig.len() != BLS12_381_G1_LEN * 2) { return Err(String::from("signature has invalid length")); } let mut cur_index = 0; let mut end_index = BLS12_381_G1_LEN; let ser_cl_h = sig[cur_index .. end_index].to_vec(); let str_cl_h = util::encode_as_hexstring(&ser_cl_h); let h = str_cl_h.as_bytes(); cur_index = end_index; end_index += BLS12_381_G1_LEN; let ser_cl_H = sig[cur_index .. end_index].to_vec(); let str_cl_H = util::encode_as_hexstring(&ser_cl_H); let H = str_cl_H.as_bytes(); let cl_sig = cl::Signature::::from_slice(&h, &H); Ok(Some(cl_sig)) } pub fn reconstruct_channel_token_bls12(channel_token: &Vec) -> BoltResult> { // parse pkc, pkm, pkM, mpk and comParams if channel_token.len() != BLS12_381_CHANNEL_TOKEN_LEN { return Err(String::from("could not reconstruct the channel token!")); } let num_y_elems = 5; let num_com_params= 6; let mut cur_index = 0; let mut end_index = SECP256K1_PK_LEN; let pkc = secp256k1::PublicKey::from_slice(&channel_token[cur_index .. end_index]).unwrap(); cur_index = end_index; end_index += SECP256K1_PK_LEN; let pkm = secp256k1::PublicKey::from_slice(&channel_token[cur_index .. end_index]).unwrap(); cur_index = end_index; end_index += BLS12_381_G2_LEN; // pk_M => (X, Y) let ser_cl_x = channel_token[cur_index .. end_index].to_vec(); let str_cl_x = util::encode_as_hexstring(&ser_cl_x); let X = str_cl_x.as_bytes(); let mut Y = Vec::new(); for _ in 0 .. num_y_elems { cur_index = end_index; end_index += BLS12_381_G2_LEN; let cl_y = channel_token[cur_index .. end_index].to_vec(); let ser_cl_y = util::encode_as_hexstring(&cl_y); let str_cl_y = ser_cl_y.as_bytes(); Y.extend(str_cl_y); } let cl_pk= cl::PublicKey::::from_slice(&X, &Y.as_slice(), str_cl_x.len(), num_y_elems); cur_index = end_index; end_index += BLS12_381_G1_LEN; let g1 = channel_token[cur_index .. end_index].to_vec(); let ser_mpk_g1 = util::encode_as_hexstring(&g1); cur_index = end_index; end_index += BLS12_381_G2_LEN; let g2 = channel_token[cur_index .. end_index].to_vec(); let ser_mpk_g2 = util::encode_as_hexstring(&g2); let ser_g1 = ser_mpk_g1.as_bytes(); let ser_g2 = ser_mpk_g2.as_bytes(); let mpk = cl::PublicParams::::from_slice(&ser_g1, &ser_g2); let mut comparams = Vec::new(); for _ in 0 .. num_com_params { cur_index = end_index; end_index += BLS12_381_G1_LEN; let com = channel_token[cur_index .. end_index].to_vec(); let ser_com = util::encode_as_hexstring(&com); let str_com = ser_com.as_bytes(); comparams.extend(str_com); } let com_params = CSMultiParams::::from_slice(&comparams.as_slice(), ser_mpk_g1.len(), num_com_params); Ok(Some(ChannelToken { pk_c: Some(pkc), pk_m: pkm, cl_pk_m: cl_pk, mpk: mpk, comParams: com_params })) } /// /// Used in open-channel WTP for validating that a close_token is a valid signature /// pub fn wtp_verify_cust_close_message(channel_token: &ChannelToken, wpk: &secp256k1::PublicKey, close_msg: &Wallet, close_token: &cl::Signature) -> bool { // close_msg => || || || || CLOSE // close_token = regular CL signature on close_msg // channel_token => // (1) check that channel token and close msg are consistent (e.g., close_msg.channelId == H(channel_token.pk_c) && let chan_token_cid = channel_token.compute_channel_id(); // util::hash_pubkey_to_fr::(&pk_c); let chan_token_wpk = util::hash_pubkey_to_fr::(&wpk); let cid_thesame = (close_msg.channelId == chan_token_cid); // (2) check that wpk matches what's in the close msg let wpk_thesame = (close_msg.wpk == chan_token_wpk); return cid_thesame && wpk_thesame && channel_token.cl_pk_m.verify(&channel_token.mpk, &close_msg.as_fr_vec(), &close_token); } pub fn wtp_generate_secp_signature(seckey: &[u8; 32], msg: &[u8; 32]) -> Vec { let secp = secp256k1::Secp256k1::signing_only(); let msg = secp256k1::Message::from_slice(msg).unwrap(); let seckey = secp256k1::SecretKey::from_slice(seckey).unwrap(); let sig = secp.sign(&msg, &seckey); // get serialized signature let ser_sig = sig.serialize_der(); return ser_sig.to_vec(); } pub fn wtp_verify_secp_signature(pubkey: &secp256k1::PublicKey, hash: &Vec, sig: &secp256k1::Signature) -> bool { let secp = secp256k1::Secp256k1::verification_only(); let msg = secp256k1::Message::from_slice(hash.as_slice()).unwrap(); return secp.verify(&msg, &sig, &pubkey).is_ok() } pub fn reconstruct_secp_channel_close_m(address: &[u8; ADDRESS_LEN], ser_revoke_token: &Vec, ser_sig: &Vec) -> ChannelcloseM { let revoke_token = secp256k1::Signature::from_der(&ser_revoke_token.as_slice()).unwrap(); let sig = secp256k1::Signature::from_der(&ser_sig.as_slice()).unwrap(); ChannelcloseM { address: hex::encode(&address.to_vec()), revoke: Some(revoke_token), signature: sig, } } } #[cfg(all(test, feature = "unstable"))] mod benches { use rand::{Rng, thread_rng}; use test::{Bencher, black_box}; #[bench] pub fn bench_one(bh: &mut Bencher) { println!("Run benchmark tests here!"); } } #[cfg(test)] mod tests { use super::*; use pairing::bls12_381::Bls12; use rand::Rng; use sha2::Digest; fn setup_new_channel_helper(channel_state: &mut bidirectional::ChannelState, init_cust_bal: i64, init_merch_bal: i64) -> (bidirectional::ChannelToken, bidirectional::MerchantState, bidirectional::CustomerState, bidirectional::ChannelState) { let rng = &mut rand::thread_rng(); let merch_name = "Bob"; let cust_name = "Alice"; let b0_cust = init_cust_bal; let b0_merch = init_merch_bal; // each party executes the init algorithm on the agreed initial challenge balance // in order to derive the channel tokens // initialize on the merchant side with balance: b0_merch let (mut channel_token, merch_state, channel_state) = bidirectional::init_merchant(rng, channel_state, merch_name); // initialize on the customer side with balance: b0_cust let cust_state = bidirectional::init_customer(rng, &mut channel_token, b0_cust, b0_merch, cust_name); return (channel_token, merch_state, cust_state, channel_state); } fn execute_establish_protocol_helper(channel_state: &mut bidirectional::ChannelState, channel_token: &mut bidirectional::ChannelToken, cust_balance: i64, merch_balance: i64, merch_state: &mut bidirectional::MerchantState, cust_state: &mut bidirectional::CustomerState) { let rng = &mut rand::thread_rng(); // lets establish the channel let (com, com_proof) = bidirectional::establish_customer_generate_proof(rng, channel_token, cust_state); // obtain close token for closing out channel //let pk_h = hash_pubkey_to_fr::(&cust_state.pk_c.clone()); let option = bidirectional::establish_merchant_issue_close_token(rng, &channel_state, &com, &com_proof, &cust_state.get_wallet().channelId, cust_balance, merch_balance, &merch_state); let close_token = match option { Ok(n) => n.unwrap(), Err(e) => panic!("Failed - bidirectional::establish_merchant_issue_close_token(): {}", e) }; assert!(cust_state.verify_close_token(&channel_state, &close_token)); // wait for funding tx to be confirmed, etc // obtain payment token for pay protocol let pay_token = bidirectional::establish_merchant_issue_pay_token(rng, &channel_state, &com, &merch_state); //assert!(cust_state.verify_pay_token(&channel_state, &pay_token)); assert!(bidirectional::establish_customer_final(channel_state, cust_state, &pay_token)); println!("Channel established!"); } fn execute_payment_protocol_helper(channel_state: &mut bidirectional::ChannelState, merch_state: &mut bidirectional::MerchantState, cust_state: &mut bidirectional::CustomerState, payment_increment: i64) { let rng = &mut rand::thread_rng(); let (payment, new_cust_state) = bidirectional::generate_payment_proof(rng, channel_state, &cust_state, payment_increment); let new_close_token = bidirectional::verify_payment_proof(rng, &channel_state, &payment, merch_state); let revoke_token = bidirectional::generate_revoke_token(&channel_state, cust_state, new_cust_state, &new_close_token); // send revoke token and get pay-token in response let new_pay_token_result: BoltResult> = bidirectional::verify_revoke_token(&revoke_token, merch_state); let new_pay_token = handle_bolt_result!(new_pay_token_result); // verify the pay token and update internal state assert!(cust_state.verify_pay_token(&channel_state, &new_pay_token.unwrap())); } #[test] fn bidirectional_payment_basics_work() { // just bidirectional case (w/o third party) let mut channel_state = bidirectional::ChannelState::::new(String::from("Channel A -> B"), false); let rng = &mut rand::thread_rng(); let b0_customer = 90; let b0_merchant = 20; let (mut channel_token, mut merch_state, mut channel_state) = bidirectional::init_merchant(rng, &mut channel_state, "Merchant Bob"); let mut cust_state = bidirectional::init_customer(rng, &mut channel_token, b0_customer, b0_merchant, "Alice"); println!("{}", cust_state); // lets establish the channel let (com, com_proof) = bidirectional::establish_customer_generate_proof(rng, &mut channel_token, &mut cust_state); // obtain close token for closing out channel let option = bidirectional::establish_merchant_issue_close_token(rng, &channel_state, &com, &com_proof, &cust_state.get_wallet().channelId, b0_customer, b0_merchant, &merch_state); let close_token = match option { Ok(n) => n.unwrap(), Err(e) => panic!("Failed - bidirectional::establish_merchant_issue_close_token(): {}", e) }; assert!(cust_state.verify_close_token(&channel_state, &close_token)); // wait for funding tx to be confirmed, etc // obtain payment token for pay protocol let pay_token = bidirectional::establish_merchant_issue_pay_token(rng, &channel_state, &com, &merch_state); //assert!(cust_state.verify_pay_token(&channel_state, &pay_token)); assert!(bidirectional::establish_customer_final(&mut channel_state, &mut cust_state, &pay_token)); println!("Channel established!"); let (payment, new_cust_state) = bidirectional::generate_payment_proof(rng, &channel_state, &cust_state, 10); let new_close_token = bidirectional::verify_payment_proof(rng, &channel_state, &payment, &mut merch_state); let revoke_token = bidirectional::generate_revoke_token(&channel_state, &mut cust_state, new_cust_state, &new_close_token); // send revoke token and get pay-token in response let new_pay_token_result: BoltResult> = bidirectional::verify_revoke_token(&revoke_token, &mut merch_state); let new_pay_token = handle_bolt_result!(new_pay_token_result); // verify the pay token and update internal state assert!(cust_state.verify_pay_token(&channel_state, &new_pay_token.unwrap())); println!("Successful payment!"); let cust_close = bidirectional::customer_close(&channel_state, &cust_state); println!("Obtained the channel close message"); println!("{}", cust_close.message); println!("{}", cust_close.signature); } #[test] fn bidirectional_multiple_payments_work() { let total_owed = 40; let b0_customer = 380; let b0_merchant = 20; let payment_increment = 20; let mut channel_state = bidirectional::ChannelState::::new(String::from("Channel A -> B"), false); // set fee for channel let fee = 5; channel_state.set_channel_fee(fee); let (mut channel_token, mut merch_state, mut cust_state, mut channel_state) = setup_new_channel_helper(&mut channel_state, b0_customer, b0_merchant); // run establish protocol for customer and merchant channel execute_establish_protocol_helper(&mut channel_state, &mut channel_token, b0_customer, b0_merchant, &mut merch_state, &mut cust_state); assert!(channel_state.channel_established); { // make multiple payments in a loop let num_payments = total_owed / payment_increment; for _i in 0..num_payments { execute_payment_protocol_helper(&mut channel_state, &mut merch_state, &mut cust_state, payment_increment); } { // scope localizes the immutable borrow here (for debug purposes only) println!("Customer balance: {:?}", &cust_state.cust_balance); println!("Merchant balance: {:?}", &cust_state.merch_balance); let total_owed_with_fees = (fee * num_payments) + total_owed; assert!(cust_state.cust_balance == (b0_customer - total_owed_with_fees) && cust_state.merch_balance == total_owed_with_fees + b0_merchant); } let cust_close_msg = bidirectional::customer_close(&channel_state, &cust_state); println!("Obtained the channel close message"); println!("{}", cust_close_msg.message); println!("{}", cust_close_msg.signature); } } #[test] fn bidirectional_payment_negative_payment_works() { // just bidirectional case (w/o third party) let total_owed = -20; let b0_customer = 90; let b0_merchant = 30; let payment_increment = -20; let mut channel_state = bidirectional::ChannelState::::new(String::from("Channel A -> B"), false); let (mut channel_token, mut merch_state, mut cust_state, mut channel_state) = setup_new_channel_helper(&mut channel_state, b0_customer, b0_merchant); // run establish protocol for customer and merchant channel execute_establish_protocol_helper(&mut channel_state, &mut channel_token, b0_customer, b0_merchant, &mut merch_state, &mut cust_state); assert!(channel_state.channel_established); { execute_payment_protocol_helper(&mut channel_state, &mut merch_state, &mut cust_state, payment_increment); { // scope localizes the immutable borrow here (for debug purposes only) println!("Customer balance: {:?}", &cust_state.cust_balance); println!("Merchant balance: {:?}", &cust_state.merch_balance); assert!(cust_state.cust_balance == (b0_customer - total_owed) && cust_state.merch_balance == total_owed + b0_merchant); } } } #[test] fn bidirectional_merchant_close_detects_double_spends() { let rng = &mut rand::thread_rng(); let b0_customer = rng.gen_range(100, 1000); let b0_merchant = 10; let pay_increment = 20; let mut channel_state = bidirectional::ChannelState::::new(String::from("Channel A -> B"), false); let (mut channel_token, mut merch_state, mut cust_state, mut channel_state) = setup_new_channel_helper(&mut channel_state, b0_customer, b0_merchant); // run establish protocol for customer and merchant channel execute_establish_protocol_helper(&mut channel_state, &mut channel_token, b0_customer, b0_merchant, &mut merch_state, &mut cust_state); assert!(channel_state.channel_established); // let's make a few payments then exit channel (will post an old channel state execute_payment_protocol_helper(&mut channel_state, &mut merch_state, &mut cust_state, pay_increment); execute_payment_protocol_helper(&mut channel_state, &mut merch_state, &mut cust_state, pay_increment); // let's close then move state forward let old_cust_close_msg = bidirectional::customer_close(&channel_state, &cust_state); execute_payment_protocol_helper(&mut channel_state, &mut merch_state, &mut cust_state, pay_increment); execute_payment_protocol_helper(&mut channel_state, &mut merch_state, &mut cust_state, pay_increment); let _cur_cust_close_msg = bidirectional::customer_close(&channel_state, &cust_state); let merch_close_result = bidirectional::merchant_close(&channel_state, &channel_token, &old_cust_close_msg, &merch_state); let merch_close_msg = match merch_close_result { Ok(n) => n.unwrap(), Err(err) => panic!("Merchant close msg: {}", err) }; println!("Double spend attempt by customer! Evidence below..."); println!("Merchant close: wpk = {}", merch_close_msg.wpk); println!("Merchant close: revoke_token = {}", merch_close_msg.revoke_token.unwrap()); } #[test] #[should_panic] fn bidirectional_merchant_close_works() { let rng = &mut rand::thread_rng(); let b0_customer = rng.gen_range(100, 1000); let b0_merchant = 10; let pay_increment = 20; let mut channel_state = bidirectional::ChannelState::::new(String::from("Channel A -> B"), false); let (mut channel_token, mut merch_state, mut cust_state, mut channel_state) = setup_new_channel_helper(&mut channel_state, b0_customer, b0_merchant); // run establish protocol for customer and merchant channel execute_establish_protocol_helper(&mut channel_state, &mut channel_token, b0_customer, b0_merchant, &mut merch_state, &mut cust_state); assert!(channel_state.channel_established); // let's make a few payments then exit channel (will post an old channel state execute_payment_protocol_helper(&mut channel_state, &mut merch_state, &mut cust_state, pay_increment); execute_payment_protocol_helper(&mut channel_state, &mut merch_state, &mut cust_state, pay_increment); execute_payment_protocol_helper(&mut channel_state, &mut merch_state, &mut cust_state, pay_increment); execute_payment_protocol_helper(&mut channel_state, &mut merch_state, &mut cust_state, pay_increment); let cust_close_msg = bidirectional::customer_close(&channel_state, &cust_state); let merch_close_result = bidirectional::merchant_close(&channel_state, &channel_token, &cust_close_msg, &merch_state); let _merch_close_msg = match merch_close_result { Ok(n) => n.unwrap(), Err(err) => panic!("Merchant close msg: {}", err) }; } #[test] fn intermediary_payment_basics_works() { println!("Intermediary test..."); let rng = &mut rand::thread_rng(); let b0_alice = rng.gen_range(100, 1000); let b0_bob = rng.gen_range(100, 1000); let b0_merch_a = rng.gen_range(100, 1000); let b0_merch_b = rng.gen_range(100, 1000); let tx_fee = rng.gen_range(1, 5); let mut channel_state = bidirectional::ChannelState::::new(String::from("New Channel State"), true); channel_state.set_channel_fee(tx_fee); let merch_name = "Hub"; // each party executes the init algorithm on the agreed initial challenge balance // in order to derive the channel tokens // initialize on the merchant side with balance: b0_merch let (mut channel_token, mut merch_state, mut channel_state) = bidirectional::init_merchant(rng, &mut channel_state, merch_name); // initialize on the customer side with balance: b0_cust let mut alice_cust_state = bidirectional::init_customer(rng, &mut channel_token, b0_alice, b0_merch_a, "Alice"); let mut bob_cust_state = bidirectional::init_customer(rng, &mut channel_token, b0_bob, b0_merch_b, "Bob"); // run establish protocol for customer and merchant channel //let mut channel_state_alice = channel_state.clone(); //let mut channel_state_bob = channel_state.clone(); execute_establish_protocol_helper(&mut channel_state, &mut channel_token, b0_alice, b0_merch_a, &mut merch_state, &mut alice_cust_state); execute_establish_protocol_helper(&mut channel_state, &mut channel_token, b0_bob, b0_merch_b, &mut merch_state, &mut bob_cust_state); assert!(channel_state.channel_established); //assert!(channel_state_bob.channel_established); // run pay protocol - flow for third-party let amount = rng.gen_range(5, 100); let (sender_payment, new_alice_cust_state) = bidirectional::generate_payment_proof(rng, &channel_state, &alice_cust_state, amount); let (receiver_payment, new_bob_cust_state) = bidirectional::generate_payment_proof(rng, &channel_state, &bob_cust_state, -amount); // TODO: figure out how to attach conditions on payment recipients close token that they must (1) produce revocation token for sender's old wallet and (2) must have channel open // intermediary executes the following on the two payment proofs let close_token_result = bidirectional::verify_multiple_payment_proofs(rng, &channel_state, &sender_payment, &receiver_payment, &mut merch_state); let (alice_close_token, bob_cond_close_token) = handle_bolt_result!(close_token_result).unwrap(); // both alice and bob generate a revoke token let revoke_token_alice = bidirectional::generate_revoke_token(&channel_state, &mut alice_cust_state, new_alice_cust_state, &alice_close_token); let revoke_token_bob = bidirectional::generate_revoke_token(&channel_state, &mut bob_cust_state, new_bob_cust_state, &bob_cond_close_token); // send both revoke tokens to intermediary and get pay-tokens in response let new_pay_token_result: BoltResult<(cl::Signature,cl::Signature)> = bidirectional::verify_multiple_revoke_tokens(&revoke_token_alice, &revoke_token_bob, &mut merch_state); let (new_pay_token_alice, new_pay_token_bob) = handle_bolt_result!(new_pay_token_result).unwrap(); // verify the pay tokens and update internal state assert!(alice_cust_state.verify_pay_token(&channel_state, &new_pay_token_alice)); assert!(bob_cust_state.verify_pay_token(&channel_state, &new_pay_token_bob)); println!("Successful payment with intermediary!"); } #[test] fn serialization_tests() { let mut channel_state = bidirectional::ChannelState::::new(String::from("Channel A -> B"), false); let rng = &mut rand::thread_rng(); let serialized = serde_json::to_string(&channel_state).unwrap(); println!("new channel state len: {}", &serialized.len()); let _chan_state: bidirectional::ChannelState = serde_json::from_str(&serialized).unwrap(); let (mut channel_token, _merch_state, _channel_state) = bidirectional::init_merchant(rng, &mut channel_state, "Merchant A"); let b0_cust = 100; let b0_merch = 10; let cust_state = bidirectional::init_customer(rng, &mut channel_token, b0_cust, b0_merch, "Customer A"); let serialized_ct = serde_json::to_string(&channel_token).unwrap(); println!("serialized ct: {:?}", &serialized_ct); let _des_ct: bidirectional::ChannelToken = serde_json::from_str(&serialized_ct).unwrap(); //println!("des_ct: {}", &des_ct); let serialized_cw = serde_json::to_string(&cust_state).unwrap(); println!("serialized cw: {:?}", &serialized_cw); let _des_cw: bidirectional::CustomerState = serde_json::from_str(&serialized_cw).unwrap(); } #[test] fn test_reconstruct_channel_token() { let _ser_channel_token = "024c252c7e36d0c30ae7c67dabea2168f41b36b85c14d3e180b423fa1a5df0e7ac027df0457901953b9b776f4999d5a1e78\ 049c0afa4f741d0d3bb7d9711a0f8c0038f4c70072363fe07ffe1450d63205cbaeaafe600ca9001d8bbf8984ce54a9c5e041084779dace7a4cf582906ea4e\ 493a1368ec7f05e7f89635c555c26e5d0149186095856dc210bef4b8fec03415cd6d1253bdafd0934a20b57ee088fa7ee0bab0668b1aa84c30e856dd685ce\ e2a95844cb68504e82fd9dd874cbf6f7ee58155245e97c52625b53f4ca969f48b33c59f0009adc70d1472a303a35ace0d96149c8cdb96f29b6f476b8f4a10\ bd430c4658d4e0b5873fcb946a76aa861c6c4c601ab8fb0b9c88d2e8861de2f0dae2bb2a8492db2978ce8f2e509328efbf12384ae2db5c17021d222724a3b\ c4b621bf4f32601d555ff2cfc2171adeb2f1bd42c484c1c0a1e5d7d2853c102080680cefc925808b6e3d71b29a93f7e8f5c2eeeeef944b3740feddb24ec2c\ 17e3db22ee6a7af77e32a9d186bdcc150dd59b0cd92b92b6656cb588dec9d1d07be5e2a319bf37f1120b7c656f78dc6c4064f8d63f590f70cdc0c1746fde6\ 035eeb9aa90b69ea666ad71b27078ab61573aec60bab80a4e6a8e4d8ce02204f5b7e0131bf24d5df1428e9e571891c6feb1c0a52ba789136b244f13f510c4\ f1f0eb4b0a7e675f105f8102c672461da340ebcae1eddd49a009bcf3b199eb2006fab6cf0ccf102b5c6dd45722dc0c27d4b9697f627f1bcbe44f6d96842de\ c92877ff23d374964970c3386972a8ae369367907001bcd8bba458b8f29842321a8231f3441054999cb19b2c40409da8216406298e1d41bcaf5ea8a225266\ 2848d3f810dd369aba5ff684360080aa6f5e9ba61be1331f6bdf8b00d1ec8453637c4b480f6d0c5e5467013aa0e8be1777c370a1988db21d8d3de3f6d79d8\ cbe6412f88d39de0cd1bf9e8f9b57ff933f21bef89b5bd3f9a901936568db58cc8326a719bf56438bbcab659a20ea5c0342eb9f072f105303c90de3b3b865\ 66155899d05d00396cfae74ac0526f0dd30c33e0c6790f3f8119dac12fb6f870b9a317afa94cd624b88ede30d49d2373b58453637c4b480f6d0c5e5467013\ aa0e8be1777c370a1988db21d8d3de3f6d79d8cbe6412f88d39de0cd1bf9e8f9b57ffa397625c859a63e2c6e42486c4f76f306d484cce151f8614f87506e9\ 9c871521dd244bfeb380481aed8df823a507c7a3ad367c1797fc6efa089f929729e7d48bfa9c60860fbb212918bb91d8c6aa523046bdf208c95fa5a0fb86a\ 1e46f92e0e5893e136b74d38e106fa990590598932a4e2458034cea22337c6f365bcb5cab59ceea03d7a9f7821ea432e262877ef0128cb73d8733c3961762\ 26acb6b3de132c803be39a4e803cbc5a4670cb6169583fa899146fab0227dc2ae167393f96f3b8b31e015af1c305de3a07f52408e9c52495c2458ea05c7a3\ 71dc14f3b1d6a646ed7cc0ca9417d8bde6efc1ac300d8e28f"; let ser_channel_token = hex::decode(_ser_channel_token).unwrap(); let option_ct = wtp_utils::reconstruct_channel_token_bls12(&ser_channel_token); let channel_token = match option_ct { Ok(n) => n.unwrap(), Err(e) => panic!("Error reconstructing compact rep of channel token: {}", e) }; let channelId = channel_token.compute_channel_id(); let original_channelId = "[\"0744645c9cbbf4e47f456fa05e2c6888a59f688641d25b2607610ce03b4ae20c\"]"; let computed_channelId = serde_json::to_string(&channelId).unwrap(); println!("channel ID: {}", channelId); println!("pkc: {:?}", channel_token.pk_c.unwrap()); println!("pkm: {:?}", channel_token.pk_m); assert_eq!(original_channelId, computed_channelId); // reconstruct signature let _ser_signature = "93f26490b4576c38dfb8dceae547f4b49aeb945ecc9cccc528c39068c78177bda68aaf45743f09c48ad99b6007fe415b\ aee9eafd51cfdb0dc567a5d152bc37861727e85088b417cf3ff57c108d0156eee56aff810f1e5f9e76cd6a3590d6db5e"; let ser_signature = hex::decode(_ser_signature).unwrap(); let option_sig = wtp_utils::reconstruct_signature_bls12(&ser_signature); let sig = match option_sig { Ok(n) => n.unwrap(), Err(e) => panic!("Error reconstructing compact rep of signature: {}", e) }; } #[test] fn test_reconstruct_secp_sig() { let _ser_sig = "3044022064650285b55624f1f64b2c75e76589fa4b1033dabaa7ff50ff026e1dc038279202204ca696e0a829687c87171e8e5dab17069be248ff2595fd9607f3346dadcb579f"; let ser_sig = hex::decode(_ser_sig).unwrap(); let signature = wtp_utils::reconstruct_secp_signature(ser_sig.as_slice()); assert_eq!(format!("{:?}", signature), _ser_sig); let sk = hex::decode("81361b9bc2f67524dcc59b980dc8b06aadb77db54f6968d2af76ecdb612e07e4").unwrap(); let msg = "hello world!"; let mut sha256 = sha2::Sha256::new(); sha256.input(msg); let mut hash = [0u8; 32]; hash.copy_from_slice(&sha256.result()); let mut seckey = [0u8; 32]; seckey.copy_from_slice(sk.as_slice()); let sig = wtp_utils::wtp_generate_secp_signature(&seckey, &hash); } #[test] fn test_reconstruct_channel_close_m() { let mut address = [0u8; 33]; let address_slice = hex::decode("0a1111111111111111111111111111111111111111111111111111111111111111").unwrap(); address.copy_from_slice(address_slice.as_slice()); let channelClose = wtp_utils::reconstruct_secp_channel_close_m(&address, &hex::decode("3044022041932b376fe2c5e9e9ad0a3804e2290c3bc40617ea4f7b913be858dbcc3760b50220429d6eb1aabbd4135db4e0776c0b768af844b0af44f2f8f9da5a65e8541b4e9f").unwrap(), &hex::decode("3045022100e76653c5f8cb4c2f39efc7c5450d4f68ef3d84d482305534f5dfc310095a3124022003c4651ce1305cffe5e483ab99925cc4c9c5df2b5449bb18a51d52b21d789716").unwrap()); assert_eq!(channelClose.address, "0a1111111111111111111111111111111111111111111111111111111111111111"); assert_eq!(format!("{:?}", channelClose.revoke.unwrap()), "3044022041932b376fe2c5e9e9ad0a3804e2290c3bc40617ea4f7b913be858dbcc3760b50220429d6eb1aabbd4135db4e0776c0b768af844b0af44f2f8f9da5a65e8541b4e9f"); assert_eq!(format!("{:?}", channelClose.signature), "3045022100e76653c5f8cb4c2f39efc7c5450d4f68ef3d84d482305534f5dfc310095a3124022003c4651ce1305cffe5e483ab99925cc4c9c5df2b5449bb18a51d52b21d789716"); } }