diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4288848..0849f20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,13 +55,19 @@ jobs: - name: Install dependencies run: | apt-get update - apt-get install -y curl gcc protobuf-compiler libsqlite3-dev + apt-get install -y curl gcc protobuf-compiler libsqlite3-dev pkg-config libssl-dev + + - name: Set environment variables for OpenSSL + run: | + echo "OPENSSL_DIR=/usr" >> $GITHUB_ENV + echo "OPENSSL_LIB_DIR=/usr/lib/x86_64-linux-gnu" >> $GITHUB_ENV - name: Install Rust Toolchain (stable) shell: bash run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - uses: Swatinem/rust-cache@v2 - name: Build civkit-cli run: cargo build --bin civkit-cli --verbose diff --git a/Cargo.toml b/Cargo.toml index f375d6c..8bef5f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,9 +29,9 @@ path = "src/services/notaryd.rs" [dependencies] futures-channel = "0.3.28" futures-util = "0.3.28" -lightning = { git = "https://github.com/civkit/rust-lightning.git", branch = "civkit-branch" } -lightning-net-tokio = { git = "https://github.com/civkit/rust-lightning.git", branch = "civkit-branch" } -lightning-invoice = { git = "https://github.com/civkit/rust-lightning.git", branch = "civkit-branch" } +lightning = { git = "https://github.com/DhananjayPurohit/rust-lightning-wizards.git", branch = "civkit-branch" } +lightning-net-tokio = { git = "https://github.com/DhananjayPurohit/rust-lightning-wizards.git", branch = "civkit-branch" } +lightning-invoice = { git = "https://github.com/DhananjayPurohit/rust-lightning-wizards.git", branch = "civkit-branch" } tokio = { version = "1", features = [ "io-util", "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] } tokio-tungstenite = "0.19.0" bitcoin = "0.29.0" @@ -45,7 +45,7 @@ serde_json = { version = "1.0" } toml = "0.5.8" serde_derive = "1.0" serde = "1.0.130" -rusqlite = "0.29.0" +rusqlite = { version = "0.29.0", features = ["bundled"] } simplelog = "0.7.1" dirs = "3.0.1" log = "0.4.14" diff --git a/src/bitcoind_client.rs b/src/bitcoind_client.rs index 96839a4..01cd474 100644 --- a/src/bitcoind_client.rs +++ b/src/bitcoind_client.rs @@ -10,7 +10,12 @@ use crate::config::Config; use crate::rpcclient::{Client, Auth}; -use tokio::sync::mpsc; +use jsonrpc::Response; + +use bitcoin::{MerkleBlock, Txid}; +use bitcoin_hashes::hex::FromHex; + +use tokio::sync::{mpsc, oneshot}; use tokio::sync::Mutex as TokioMutex; use tokio::time::{sleep, Duration}; @@ -18,6 +23,7 @@ use tokio::time::{sleep, Duration}; #[derive(Debug)] pub enum BitcoindRequest { CheckRpcCall, + GenerateTxInclusionProof { txid: String, respond_to: oneshot::Sender> }, } pub struct BitcoindClient { @@ -71,9 +77,10 @@ impl BitcoindHandler { let separator = ":"; let url = bitcoind_client.host.clone() + &separator + &bitcoind_client.port.clone(); - println!("Client url {}", url); - let rpc_client = Client::new(&url, Auth::None).unwrap(); + let user_pass = Auth::UserPass(bitcoind_client.rpc_user.clone(), bitcoind_client.rpc_password.clone()); + + let rpc_client = Client::new(&url, user_pass).unwrap(); BitcoindHandler { receive_bitcoind_request: TokioMutex::new(receive_bitcoind_requests), @@ -91,10 +98,29 @@ impl BitcoindHandler { if let Ok(bitcoind_request) = receive_bitcoind_request_lock.await.try_recv() { match bitcoind_request { BitcoindRequest::CheckRpcCall => { - println!("[CIVKITD] - BITCOIND CLIENT: Received rpc call"); + println!("[CIVKITD] - BITCOIND CLIENT: Received rpc call - Test bitcoind"); self.rpc_client.call("getblockchaininfo", &vec![]); - } + }, + BitcoindRequest::GenerateTxInclusionProof { txid, respond_to } => { + println!("[CIVKITD] - BITCOIND CLIENT: Received rpc call - Generate merkle block"); + + let txid_json_value = serde_json::to_value(txid).unwrap(); + let txid_json = serde_json::Value::Array(vec![txid_json_value]); + + if let Ok(response) = self.rpc_client.call("gettxoutproof", &[txid_json]) { + if let Some(raw_value) = response.result { + let mut mb_string = raw_value.get().to_string(); + let index = mb_string.find('\"').unwrap(); + mb_string.remove(index); + let index = mb_string.find('\"').unwrap(); + mb_string.remove(index); + //let mb_bytes = Vec::from_hex(&mb_string).unwrap(); + //let mb: MerkleBlock = bitcoin::consensus::deserialize(&mb_bytes).unwrap(); + respond_to.send(Some(mb_string)); + } + } else { respond_to.send(None); } + }, _ => {}, } } diff --git a/src/client.rs b/src/client.rs index f0d730e..d7cfac3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -9,7 +9,7 @@ use adminctrl::admin_ctrl_client::AdminCtrlClient; //TODO: simplify by using prefix -use adminctrl::{PingRequest, PongRequest, ShutdownRequest, ShutdownReply, SendNote, ReceivedNote, ListClientRequest, ListSubscriptionRequest, PeerConnectionRequest, DisconnectClientRequest, SendNotice, SendOffer, SendInvoice, ListDbEventsRequest, ListDbClientsRequest, ListDbClientsReply, CheckTxidInclusionRequest}; +use adminctrl::{PingRequest, PongRequest, ShutdownRequest, ShutdownReply, SendNote, ReceivedNote, ListClientRequest, ListSubscriptionRequest, PeerConnectionRequest, DisconnectClientRequest, SendNotice, SendOffer, SendInvoice, ListDbEventsRequest, ListDbClientsRequest, ListDbClientsReply, CheckChainStateRequest, CheckChainStateReply, GenerateTxInclusionProofRequest, GenerateTxInclusionProofReply}; use std::env; use std::process; @@ -78,9 +78,11 @@ enum Command { ListDbEvents, /// List DB clients entries ListDbClients, - /// Check txid inclusion - CheckTxidInclusion { - txid: Vec, + /// Check chain state is available + CheckChainState, + /// Generate a merkle block (header + merkle branch) for the target txid + GenerateTxInclusionProof { + txid: String, } } @@ -206,12 +208,19 @@ async fn main() -> Result<(), Box> { let _response = client.list_db_clients(request).await?; } - Command::CheckTxidInclusion { txid } => { - let request = tonic::Request::new(CheckTxidInclusionRequest { - txid: txid.to_vec(), + Command::CheckChainState => { + let request = tonic::Request::new(CheckChainStateRequest {}); + + let _response = client.check_chain_state(request).await?; + } + Command::GenerateTxInclusionProof { txid } => { + let request = tonic::Request::new(GenerateTxInclusionProofRequest { + txid: txid }); - let _response = client.check_txid_inclusion(request).await?; + if let Ok(response) = client.generate_tx_inclusion_proof(request).await { + println!("tx inclusion proof: {}", response.into_inner().merkle_block); + } } } Ok(()) diff --git a/src/config.rs b/src/config.rs index 8daa14d..83c9894 100644 --- a/src/config.rs +++ b/src/config.rs @@ -89,12 +89,12 @@ impl Default for Config { base_pubkey: "031dd94c5262454986a2f0a6c557d2cbe41ec5a8131c588b9367c9310125a8a7dc".to_string(), chain_code: "0a090f710e47968aee906804f211cf10cde9a11e14908ca0f78cc55dd190ceaa".to_string(), }, - bitcoind_params: BitcoindParams { - host: "http://127.0.0.1".to_string(), - port: "18443".to_string(), // regtest - rpc_user: "civkitd_client".to_string(), - rpc_password: "hello_world".to_string(), - } + bitcoind_params: BitcoindParams { + host: "https://127.0.0.1".to_string(), + port: "18443".to_string(), // regtest + rpc_user: "civkitd_client".to_string(), + rpc_password: "hello_world".to_string(), + } } } } diff --git a/src/credentialgateway.rs b/src/credentialgateway.rs index 92460c2..47c15c6 100644 --- a/src/credentialgateway.rs +++ b/src/credentialgateway.rs @@ -18,12 +18,14 @@ use bitcoin::network::constants::Network; use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1}; use bitcoin::secp256k1; -use nostr::{Event, Kind}; +use nostr::{Event, Kind, Tag}; use staking_credentials::common::msgs::{AssetProofFeatures, CredentialsFeatures, CredentialPolicy, ServicePolicy}; +use staking_credentials::common::utils::Proof; use staking_credentials::issuance::issuerstate::IssuerState; -use staking_credentials::common::msgs::{CredentialAuthenticationResult, ServiceDeliveranceResult}; +use staking_credentials::common::msgs::{CredentialAuthenticationResult, CredentialAuthenticationPayload, Decodable, ServiceDeliveranceResult}; +use staking_credentials::common::utils::Credentials; use crate::events::ClientEvents; use crate::bitcoind_client::BitcoindClient; @@ -52,24 +54,50 @@ impl Default for GatewayConfig { } } +struct IssuanceRequest { + client_id: u64, + pending_credentials: Vec, +} + +enum IssuanceError { + InvalidDataCarrier, + Parse, + Policy +} + +const MAX_CREDENTIALS_PER_REQUEST: usize = 100; + +//TODO: protect denial-of-service from client id requests congestion rate struct IssuanceManager { request_counter: u64, - table_signing_requests: HashMap,//TODO: add Txid + table_signing_requests: HashMap, issuance_engine: IssuerState, } impl IssuanceManager { - fn register_authentication_request(&mut self, client_id: u64, ev: Event) -> Result<(u64, Txid), ()> { + fn register_authentication_request(&mut self, client_id: u64, ev: Event) -> Result<(u64, Proof), IssuanceError> { let request_id = self.request_counter; - self.table_signing_requests.insert(self.request_counter, client_id); + + if ev.tags.len() == 1 { + return Err(IssuanceError::InvalidDataCarrier); + } + let credential_msg_bytes = match &ev.tags[0] { + Tag::Credential(credential_bytes) => { credential_bytes }, + _ => { return Err(IssuanceError::InvalidDataCarrier); }, + }; + let credential_authentication = if let Ok(credential_authentication) = CredentialAuthenticationPayload::decode(&credential_msg_bytes) { + credential_authentication + } else { return Err(IssuanceError::Parse); }; + + if credential_authentication.credentials.len() > MAX_CREDENTIALS_PER_REQUEST { + return Err(IssuanceError::Policy); + } + + self.table_signing_requests.insert(self.request_counter, IssuanceRequest { client_id, pending_credentials: credential_authentication.credentials }); self.request_counter += 1; - //TODO: verify we hash 32 byte from event - let mut enc = Txid::engine(); - enc.input(ev.content.as_bytes()); - //TODO: verify we support the proof and credentials - Ok((request_id, Txid::from_engine(enc))) + Ok((request_id, credential_authentication.proof)) } fn validate_authentication_request(&mut self, request_id: u64, result: bool) -> Result { @@ -85,8 +113,8 @@ impl IssuanceManager { Err(()) } fn get_client_id(&self, request_id: u64) -> u64 { - if let Some(client_id) = self.table_signing_requests.get(&request_id) { - *client_id + if let Some(issuance_request) = self.table_signing_requests.get(&request_id) { + issuance_request.client_id } else { 0 } } } @@ -188,9 +216,9 @@ impl CredentialGateway { match event { ClientEvents::Credential { client_id, event } => { if event.kind == Kind::CredentialAuthenticationRequest { - if let Ok(txid) = self.issuance_manager.register_authentication_request(client_id, event) { + if let Ok(proof) = self.issuance_manager.register_authentication_request(client_id, event) { println!("[CIVKITD] - CREDENTIAL: txid to verify"); - proofs_to_verify.push(txid); + proofs_to_verify.push(proof); } } else if event.kind == Kind::ServiceDeliveranceRequest { // For now validate directly are all information self-contained in redemption manager. diff --git a/src/proto/adminctrl.proto b/src/proto/adminctrl.proto index 02f1e3a..aaa6eb9 100644 --- a/src/proto/adminctrl.proto +++ b/src/proto/adminctrl.proto @@ -20,7 +20,8 @@ service AdminCtrl { rpc PublishInvoice (SendInvoice) returns (ReceivedInvoice); rpc ListDbEvents (ListDbEventsRequest) returns (ListDbEventsReply); rpc ListDbClients (ListDbClientsRequest) returns (ListDbClientsReply); - rpc CheckTxidInclusion (CheckTxidInclusionRequest) returns (CheckTxidInclusionReply); + rpc CheckChainState (CheckChainStateRequest) returns (CheckChainStateReply); + rpc GenerateTxInclusionProof (GenerateTxInclusionProofRequest) returns (GenerateTxInclusionProofReply); } message PingRequest { @@ -129,9 +130,16 @@ message ListDbClientsRequest { message ListDbClientsReply { } -message CheckTxidInclusionRequest { - bytes txid = 1; +message CheckChainStateRequest { } -message CheckTxidInclusionReply { +message CheckChainStateReply { +} + +message GenerateTxInclusionProofRequest { + string txid = 1; +} + +message GenerateTxInclusionProofReply { + string merkle_block = 1; } diff --git a/src/rpcclient.rs b/src/rpcclient.rs index 3d972bb..54f8363 100644 --- a/src/rpcclient.rs +++ b/src/rpcclient.rs @@ -10,6 +10,7 @@ /// Simple utilities to query bitcoin RPC API. Inspired by the bitcoin-rpc /// crate. + use jsonrpc; #[derive(Clone, Debug, Hash, Eq, PartialEq)] @@ -34,6 +35,7 @@ pub enum Error { Json(serde_json::error::Error), } + pub struct Client { client: jsonrpc::client::Client, } @@ -49,7 +51,7 @@ impl Client { } else { return Err(Error::InvalidUserPass) } } - pub fn call(&self, cmd: &str, args: &[serde_json::Value]) -> Result<(), ()> { + pub fn call(&self, cmd: &str, args: &[serde_json::Value]) -> Result { if let Ok(raw_args) = args.iter().map(|a| { let json_string = serde_json::to_string(a)?; @@ -57,12 +59,15 @@ impl Client { }).map(|a| a.map_err(|e| Error::Json(e))).collect::, Error>>() { let req = self.client.build_request(&cmd, &raw_args); - - let resp = self.client.send_request(req); - println!("resp {:?}", resp.unwrap()); + println!("req {:?}", req); + + if let Ok(resp) = self.client.send_request(req) { + //println!("resp {:?}", resp); + return Ok(resp); + } } - Ok(()) + Err(()) } } diff --git a/src/sample.rs b/src/sample.rs index 08dc6fc..8b20b80 100644 --- a/src/sample.rs +++ b/src/sample.rs @@ -15,11 +15,12 @@ use std::process; use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1, Signature}; use bitcoin::secp256k1::Message as SecpMessage; use bitcoin::blockdata::transaction::Transaction; -use bitcoin::Txid; +use bitcoin::{MerkleBlock, Txid}; use bitcoin::hashes::{Hash, sha256, HashEngine}; +use bitcoin_hashes::hex::FromHex; use staking_credentials::common::utils::{Credentials, Proof}; -use staking_credentials::common::msgs::{CredentialAuthenticationPayload, ServiceDeliveranceRequest}; +use staking_credentials::common::msgs::{CredentialAuthenticationPayload, Encodable, ServiceDeliveranceRequest}; use nostr::{RelayMessage, EventBuilder, Metadata, Keys, ClientMessage, Kind, Filter, SubscriptionId, Timestamp, Tag}; @@ -145,7 +146,7 @@ fn cli() -> Command { ) .subcommand( Command::new("submitcredentialproof") - .args([Arg::new("txid").help("The transaction id").required(true)]) + .args([Arg::new("merkle_block").help("The merkle block").required(true)]) .help_template(APPLET_TEMPLATE) .about("Submit a credential proof to the relay"), ) @@ -261,9 +262,7 @@ fn respond( let until = Timestamp::from_str(until_raw.unwrap()).unwrap(); let filter = Filter::new().kinds(kinds).since(since).until(until); let client_message = ClientMessage::new_req(id, vec![filter]); - let serialized_message = client_message.as_json(); - tx.unbounded_send(Message::text(serialized_message)) - .unwrap(); + let serialized_message = client_message.as_json(); tx.unbounded_send(Message::text(serialized_message)) .unwrap(); } Some(("closesubscription", matches)) => { let subscriptionid: Option<&String> = matches.get_one("subscriptionid"); @@ -280,24 +279,23 @@ fn respond( return Ok(true); } Some(("submitcredentialproof", matches)) => { - let txid_parse: Option<&String> = matches.get_one("txid"); - let txid_str = txid_parse.unwrap(); - - let bytes = txid_str.as_bytes(); - let mut enc = Txid::engine(); - enc.input(&bytes); - let txid = Txid::from_engine(enc); + let mb_parse: Option<&String> = matches.get_one("merkle_block"); + let mb_str = mb_parse.unwrap(); - let proof = Proof::Txid(txid); + let mb_bytes = Vec::from_hex(mb_str).unwrap(); + let mb: MerkleBlock = bitcoin::consensus::deserialize(&mb_bytes).unwrap(); + + let proof = Proof::MerkleBlock(mb); //TODO: get credentials from sample local holder state let credentials = vec![Credentials([16;32])]; let credential_authentication = CredentialAuthenticationPayload::new(proof, credentials); - //TODO: credential_authentication.serialize() + let mut buffer = vec![]; + credential_authentication.encode(&mut buffer); let tags = &[ - Tag::Credential(vec![]), + Tag::Credential(buffer), ]; if let Ok(credential_carrier) = diff --git a/src/server.rs b/src/server.rs index dc5868a..00c2923 100644 --- a/src/server.rs +++ b/src/server.rs @@ -13,6 +13,8 @@ mod servicemanager; mod config; mod util; +use bitcoin::MerkleBlock; + use crate::util::init_logger; use log; use std::fs; @@ -259,16 +261,32 @@ impl AdminCtrl for ServiceManager { Ok(Response::new(adminctrl::ListDbClientsReply {})) } - async fn check_txid_inclusion(&self, request: Request) -> Result, Status> { + async fn check_chain_state(&self, request: Request) -> Result, Status> { - println!("[CIVKITD] - CONTROL: check txid inclusion !"); + println!("[CIVKITD] - CONTROL: check chain state !"); { let mut send_bitcoind_request_lock = self.send_bitcoind_request.lock().unwrap(); send_bitcoind_request_lock.send(BitcoindRequest::CheckRpcCall); } - Ok(Response::new(adminctrl::CheckTxidInclusionReply {})) + Ok(Response::new(adminctrl::CheckChainStateReply {})) + } + + async fn generate_tx_inclusion_proof(&self, request: Request) -> Result, Status> { + + let txid = request.into_inner().txid; + + println!("[CIVKITD] - CONTROL: generate tx inclusion proof!"); + + let (send, recv) = oneshot::channel::>(); + { + let mut send_bitcoind_request_lock = self.send_bitcoind_request.lock().unwrap(); + send_bitcoind_request_lock.send(BitcoindRequest::GenerateTxInclusionProof { txid: txid, respond_to: send }); + } + if let Some(response) = recv.await.expect("BitcoindHandler has been killed") { + Ok(Response::new(adminctrl::GenerateTxInclusionProofReply { merkle_block: response } )) + } else { Ok(Response::new(adminctrl::GenerateTxInclusionProofReply { merkle_block: String::new() })) } } }