chaincraft_rust/examples/
ecdsa_ledger.rs1use crate::{
7 crypto::ecdsa::{ECDSASignature, ECDSAVerifier},
8 error::{ChaincraftError, Result},
9 shared::{SharedMessage, SharedObjectId},
10 shared_object::ApplicationObject,
11};
12use async_trait::async_trait;
13use serde::{Deserialize, Serialize};
14use serde_json::Value;
15use std::any::Any;
16use std::collections::{HashMap, HashSet};
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(tag = "message_type")]
20pub enum LedgerMessageType {
21 #[serde(rename = "TRANSFER")]
22 Transfer {
23 from: String,
24 to: String,
25 amount: u64,
26 nonce: u64,
27 public_key_pem: String,
28 #[serde(default)]
29 signature: String,
30 },
31}
32
33#[derive(Debug, Clone)]
34pub struct LedgerEntry {
35 pub from: String,
36 pub to: String,
37 pub amount: u64,
38 pub nonce: u64,
39}
40
41#[derive(Debug, Clone)]
44pub struct ECDSALedgerObject {
45 id: SharedObjectId,
46 entries: Vec<LedgerEntry>,
47 seen_tx_hashes: HashSet<String>,
48 balances: HashMap<String, u64>,
49 nonces: HashMap<String, u64>,
50 verifier: ECDSAVerifier,
51}
52
53impl ECDSALedgerObject {
54 pub fn new() -> Self {
55 Self {
56 id: SharedObjectId::new(),
57 entries: Vec::new(),
58 seen_tx_hashes: HashSet::new(),
59 balances: HashMap::new(),
60 nonces: HashMap::new(),
61 verifier: ECDSAVerifier::new(),
62 }
63 }
64
65 pub fn entries(&self) -> &[LedgerEntry] {
66 &self.entries
67 }
68
69 pub fn balance(&self, account: &str) -> u64 {
70 *self.balances.get(account).unwrap_or(&0)
71 }
72
73 fn tx_hash(from: &str, to: &str, amount: u64, nonce: u64) -> String {
74 format!("{}:{}:{}:{}", from, to, amount, nonce)
75 }
76
77 fn validate_signature(&self, msg_data: &Value, signature: &str, public_key_pem: &str) -> Result<bool> {
78 let mut for_verify = msg_data.clone();
79 if let Some(obj) = for_verify.as_object_mut() {
80 obj.remove("signature");
81 }
82 let payload = serde_json::to_string(&for_verify)
83 .map_err(|e| ChaincraftError::Serialization(crate::error::SerializationError::Json(e)))?;
84 let sig_bytes = hex::decode(signature).map_err(|_| ChaincraftError::validation("Invalid signature hex"))?;
85 let ecdsa_sig = ECDSASignature::from_bytes(&sig_bytes)
86 .map_err(|_| ChaincraftError::validation("Invalid signature format"))?;
87 self.verifier.verify(payload.as_bytes(), &ecdsa_sig, public_key_pem)
88 }
89}
90
91impl Default for ECDSALedgerObject {
92 fn default() -> Self {
93 Self::new()
94 }
95}
96
97#[async_trait]
98impl ApplicationObject for ECDSALedgerObject {
99 fn id(&self) -> &SharedObjectId {
100 &self.id
101 }
102
103 fn type_name(&self) -> &'static str {
104 "ECDSALedger"
105 }
106
107 async fn is_valid(&self, message: &SharedMessage) -> Result<bool> {
108 let msg: LedgerMessageType = serde_json::from_value(message.data.clone())
109 .map_err(|_| ChaincraftError::validation("Invalid ledger message format"))?;
110
111 match msg {
112 LedgerMessageType::Transfer { from, to, amount, nonce, public_key_pem, signature } => {
113 if signature.is_empty() {
114 return Ok(false);
115 }
116 let tx_hash = Self::tx_hash(&from, &to, amount, nonce);
117 if self.seen_tx_hashes.contains(&tx_hash) {
118 return Ok(true);
119 }
120 let msg_data = serde_json::to_value(&message.data).unwrap_or_default();
121 self.validate_signature(&msg_data, &signature, &public_key_pem)
122 }
123 }
124 }
125
126 async fn add_message(&mut self, message: SharedMessage) -> Result<()> {
127 let msg: LedgerMessageType = serde_json::from_value(message.data.clone())
128 .map_err(|_| ChaincraftError::validation("Invalid ledger message format"))?;
129
130 match msg {
131 LedgerMessageType::Transfer {
132 from,
133 to,
134 amount,
135 nonce,
136 public_key_pem,
137 signature,
138 } => {
139 let tx_hash = Self::tx_hash(&from, &to, amount, nonce);
140 if self.seen_tx_hashes.contains(&tx_hash) {
141 return Ok(());
142 }
143
144 let msg_data = message.data.clone();
145 if !self.validate_signature(&msg_data, &signature, &public_key_pem)? {
146 return Ok(());
147 }
148
149 let from_balance = *self.balances.get(&from).unwrap_or(&0);
150 let expected_nonce = *self.nonces.get(&from).unwrap_or(&0);
151 if nonce != expected_nonce {
152 return Ok(());
153 }
154 if from_balance < amount && from_balance > 0 {
156 return Ok(());
157 }
158
159 self.seen_tx_hashes.insert(tx_hash);
160 self.entries.push(LedgerEntry {
161 from: from.clone(),
162 to: to.clone(),
163 amount,
164 nonce,
165 });
166 if from_balance >= amount {
167 self.balances.insert(from.clone(), from_balance - amount);
168 }
169 *self.balances.entry(to).or_insert(0) += amount;
170 self.nonces.insert(from, nonce + 1);
171 Ok(())
172 }
173 }
174 }
175
176 fn is_merkleized(&self) -> bool {
177 false
178 }
179
180 async fn get_latest_digest(&self) -> Result<String> {
181 Ok(self.entries.len().to_string())
182 }
183
184 async fn has_digest(&self, _digest: &str) -> Result<bool> {
185 Ok(false)
186 }
187
188 async fn is_valid_digest(&self, _digest: &str) -> Result<bool> {
189 Ok(true)
190 }
191
192 async fn add_digest(&mut self, _digest: String) -> Result<bool> {
193 Ok(false)
194 }
195
196 async fn gossip_messages(&self, _digest: Option<&str>) -> Result<Vec<SharedMessage>> {
197 Ok(Vec::new())
198 }
199
200 async fn get_messages_since_digest(&self, _digest: &str) -> Result<Vec<SharedMessage>> {
201 Ok(Vec::new())
202 }
203
204 async fn get_state(&self) -> Result<Value> {
205 let balances: HashMap<&str, u64> = self.balances.iter().map(|(k, v)| (k.as_str(), *v)).collect();
206 Ok(serde_json::json!({
207 "entry_count": self.entries.len(),
208 "balances": balances
209 }))
210 }
211
212 async fn reset(&mut self) -> Result<()> {
213 self.entries.clear();
214 self.seen_tx_hashes.clear();
215 self.balances.clear();
216 self.nonces.clear();
217 Ok(())
218 }
219
220 fn clone_box(&self) -> Box<dyn ApplicationObject> {
221 Box::new(self.clone())
222 }
223
224 fn as_any(&self) -> &dyn Any {
225 self
226 }
227
228 fn as_any_mut(&mut self) -> &mut dyn Any {
229 self
230 }
231}
232
233pub mod helpers {
235 use super::*;
236 use crate::crypto::ecdsa::ECDSASigner;
237 use serde_json::json;
238
239 pub fn create_transfer(
241 from: String,
242 to: String,
243 amount: u64,
244 nonce: u64,
245 signer: &ECDSASigner,
246 ) -> Result<serde_json::Value> {
247 let public_key_pem = signer.get_public_key_pem()?;
248 let payload = json!({
249 "message_type": "TRANSFER",
250 "from": from,
251 "to": to,
252 "amount": amount,
253 "nonce": nonce,
254 "public_key_pem": public_key_pem,
255 "signature": ""
256 });
257 let mut for_sign = payload.clone();
258 if let Some(obj) = for_sign.as_object_mut() {
259 obj.remove("signature");
260 }
261 let to_sign = serde_json::to_vec(&for_sign)
262 .map_err(|e| ChaincraftError::Serialization(crate::error::SerializationError::Json(e)))?;
263 let sig = signer.sign(&to_sign)?;
264 let sig_hex = hex::encode(sig.to_bytes());
265 let mut out = payload;
266 out["signature"] = serde_json::json!(sig_hex);
267 Ok(out)
268 }
269}