1#![allow(clippy::unwrap_used)]
8
9use super::{BinaryWriter, DecodingError, EncodingError, FieldReader};
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15pub struct TransactionEnvelope {
16 pub header: TransactionHeader,
17 pub body: Value,
18 pub signatures: Vec<TransactionSignature>,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
23pub struct TransactionHeader {
24 pub principal: String,
25 pub initiator: Option<String>,
26 pub timestamp: u64,
27 pub nonce: Option<u64>,
28 pub memo: Option<String>,
29 pub metadata: Option<Value>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
34pub struct TransactionSignature {
35 pub signature: Vec<u8>,
36 pub signer: String,
37 pub timestamp: u64,
38 pub vote: Option<String>,
39 pub public_key: Option<Vec<u8>>,
40 pub key_page: Option<TransactionKeyPage>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
45pub struct TransactionKeyPage {
46 pub height: u64,
47 pub index: u32,
48}
49
50#[derive(Debug, Clone, Copy)]
52pub struct TransactionCodec;
53
54impl TransactionCodec {
55 pub fn encode_envelope(envelope: &TransactionEnvelope) -> Result<Vec<u8>, EncodingError> {
58 let mut writer = BinaryWriter::new();
59
60 let header_data = Self::encode_header(&envelope.header)?;
62 writer.write_bytes_field(&header_data, 1)?;
63
64 let body_json =
66 serde_json::to_vec(&envelope.body).map_err(|_| EncodingError::InvalidUtf8)?;
67 writer.write_bytes_field(&body_json, 2)?;
68
69 for signature in &envelope.signatures {
71 let sig_data = Self::encode_signature(signature)?;
72 writer.write_bytes_field(&sig_data, 3)?;
73 }
74
75 Ok(writer.into_bytes())
76 }
77
78 pub fn decode_envelope(data: &[u8]) -> Result<TransactionEnvelope, DecodingError> {
81 let field_reader = FieldReader::new(data)?;
82
83 let header_data = field_reader
85 .get_field(1)
86 .ok_or(DecodingError::UnexpectedEof)?;
87 let header = Self::decode_header(header_data)?;
88
89 let body_data = field_reader
91 .read_bytes_field(2)?
92 .ok_or(DecodingError::UnexpectedEof)?;
93 let body: Value =
94 serde_json::from_slice(&body_data).map_err(|_| DecodingError::InvalidUtf8)?;
95
96 let mut signatures = Vec::new();
98 for field_num in field_reader.field_numbers() {
99 if field_num == 3 {
100 if let Some(sig_data) = field_reader.get_field(field_num) {
101 signatures.push(Self::decode_signature(sig_data)?);
102 }
103 }
104 }
105
106 Ok(TransactionEnvelope {
107 header,
108 body,
109 signatures,
110 })
111 }
112
113 pub fn encode_header(header: &TransactionHeader) -> Result<Vec<u8>, EncodingError> {
116 let mut writer = BinaryWriter::new();
117
118 writer.write_string_field(&header.principal, 1)?;
120
121 if let Some(ref initiator) = header.initiator {
123 writer.write_string_field(initiator, 2)?;
124 }
125
126 writer.write_uvarint_field(header.timestamp, 3)?;
128
129 if let Some(nonce) = header.nonce {
131 writer.write_uvarint_field(nonce, 4)?;
132 }
133
134 if let Some(ref memo) = header.memo {
136 writer.write_string_field(memo, 5)?;
137 }
138
139 if let Some(ref metadata) = header.metadata {
141 let metadata_json =
142 serde_json::to_vec(metadata).map_err(|_| EncodingError::InvalidUtf8)?;
143 writer.write_bytes_field(&metadata_json, 6)?;
144 }
145
146 Ok(writer.into_bytes())
147 }
148
149 pub fn decode_header(data: &[u8]) -> Result<TransactionHeader, DecodingError> {
152 let field_reader = FieldReader::new(data)?;
153
154 let principal = field_reader
155 .read_string_field(1)?
156 .ok_or(DecodingError::UnexpectedEof)?;
157
158 let initiator = field_reader.read_string_field(2)?;
159
160 let timestamp = field_reader
161 .read_uvarint_field(3)?
162 .ok_or(DecodingError::UnexpectedEof)?;
163
164 let nonce = field_reader.read_uvarint_field(4)?;
165
166 let memo = field_reader.read_string_field(5)?;
167
168 let metadata = if let Some(metadata_bytes) = field_reader.read_bytes_field(6)? {
169 let metadata: Value =
170 serde_json::from_slice(&metadata_bytes).map_err(|_| DecodingError::InvalidUtf8)?;
171 Some(metadata)
172 } else {
173 None
174 };
175
176 Ok(TransactionHeader {
177 principal,
178 initiator,
179 timestamp,
180 nonce,
181 memo,
182 metadata,
183 })
184 }
185
186 pub fn encode_signature(signature: &TransactionSignature) -> Result<Vec<u8>, EncodingError> {
189 let mut writer = BinaryWriter::new();
190
191 writer.write_bytes_field(&signature.signature, 1)?;
193
194 writer.write_string_field(&signature.signer, 2)?;
196
197 writer.write_uvarint_field(signature.timestamp, 3)?;
199
200 if let Some(ref vote) = signature.vote {
202 writer.write_string_field(vote, 4)?;
203 }
204
205 if let Some(ref public_key) = signature.public_key {
207 writer.write_bytes_field(public_key, 5)?;
208 }
209
210 if let Some(ref key_page) = signature.key_page {
212 let key_page_data = Self::encode_key_page(key_page)?;
213 writer.write_bytes_field(&key_page_data, 6)?;
214 }
215
216 Ok(writer.into_bytes())
217 }
218
219 pub fn decode_signature(data: &[u8]) -> Result<TransactionSignature, DecodingError> {
222 let field_reader = FieldReader::new(data)?;
223
224 let signature = field_reader
225 .read_bytes_field(1)?
226 .ok_or(DecodingError::UnexpectedEof)?;
227
228 let signer = field_reader
229 .read_string_field(2)?
230 .ok_or(DecodingError::UnexpectedEof)?;
231
232 let timestamp = field_reader
233 .read_uvarint_field(3)?
234 .ok_or(DecodingError::UnexpectedEof)?;
235
236 let vote = field_reader.read_string_field(4)?;
237
238 let public_key = field_reader.read_bytes_field(5)?;
239
240 let key_page = if let Some(key_page_data) = field_reader.get_field(6) {
241 Some(Self::decode_key_page(key_page_data)?)
242 } else {
243 None
244 };
245
246 Ok(TransactionSignature {
247 signature,
248 signer,
249 timestamp,
250 vote,
251 public_key,
252 key_page,
253 })
254 }
255
256 pub fn encode_key_page(key_page: &TransactionKeyPage) -> Result<Vec<u8>, EncodingError> {
258 let mut writer = BinaryWriter::new();
259
260 writer.write_uvarint_field(key_page.height, 1)?;
262
263 writer.write_uvarint_field(key_page.index as u64, 2)?;
265
266 Ok(writer.into_bytes())
267 }
268
269 pub fn decode_key_page(data: &[u8]) -> Result<TransactionKeyPage, DecodingError> {
271 let field_reader = FieldReader::new(data)?;
272
273 let height = field_reader
274 .read_uvarint_field(1)?
275 .ok_or(DecodingError::UnexpectedEof)?;
276
277 let index = field_reader
278 .read_uvarint_field(2)?
279 .ok_or(DecodingError::UnexpectedEof)? as u32;
280
281 Ok(TransactionKeyPage { height, index })
282 }
283
284 pub fn get_transaction_hash(envelope: &TransactionEnvelope) -> Result<[u8; 32], EncodingError> {
287 let mut writer = BinaryWriter::new();
289
290 let header_data = Self::encode_header(&envelope.header)?;
291 writer.write_bytes_field(&header_data, 1)?;
292
293 let body_json =
294 serde_json::to_vec(&envelope.body).map_err(|_| EncodingError::InvalidUtf8)?;
295 writer.write_bytes_field(&body_json, 2)?;
296
297 let data = writer.into_bytes();
298 Ok(crate::codec::sha256_bytes(&data))
299 }
300
301 pub fn create_envelope(
303 principal: String,
304 body: Value,
305 timestamp: Option<u64>,
306 ) -> TransactionEnvelope {
307 let timestamp = timestamp.unwrap_or_else(|| {
308 std::time::SystemTime::now()
309 .duration_since(std::time::UNIX_EPOCH)
310 .unwrap()
311 .as_millis() as u64
312 });
313
314 TransactionEnvelope {
315 header: TransactionHeader {
316 principal,
317 initiator: None,
318 timestamp,
319 nonce: None,
320 memo: None,
321 metadata: None,
322 },
323 body,
324 signatures: Vec::new(),
325 }
326 }
327
328 pub fn add_signature(
330 envelope: &mut TransactionEnvelope,
331 signature: Vec<u8>,
332 signer: String,
333 public_key: Option<Vec<u8>>,
334 ) {
335 let timestamp = std::time::SystemTime::now()
336 .duration_since(std::time::UNIX_EPOCH)
337 .unwrap()
338 .as_millis() as u64;
339
340 envelope.signatures.push(TransactionSignature {
341 signature,
342 signer,
343 timestamp,
344 vote: None,
345 public_key,
346 key_page: None,
347 });
348 }
349
350 pub fn validate_envelope(envelope: &TransactionEnvelope) -> Result<(), String> {
352 if envelope.header.principal.is_empty() {
354 return Err("Principal is required".to_string());
355 }
356
357 if envelope.header.timestamp == 0 {
358 return Err("Timestamp is required".to_string());
359 }
360
361 for (i, sig) in envelope.signatures.iter().enumerate() {
363 if sig.signature.is_empty() {
364 return Err(format!("Signature {} is empty", i));
365 }
366
367 if sig.signer.is_empty() {
368 return Err(format!("Signer {} is empty", i));
369 }
370
371 if sig.timestamp == 0 {
372 return Err(format!("Signature {} timestamp is required", i));
373 }
374 }
375
376 Ok(())
377 }
378}
379
380#[derive(Debug, Clone, Copy)]
382pub struct TransactionBodyBuilder;
383
384impl TransactionBodyBuilder {
385 pub fn send_tokens(to: Vec<TokenRecipient>) -> Value {
387 serde_json::json!({
388 "type": "send-tokens",
389 "to": to
390 })
391 }
392
393 pub fn create_identity(url: String, key_book_url: String) -> Value {
395 serde_json::json!({
396 "type": "create-identity",
397 "url": url,
398 "keyBook": key_book_url
399 })
400 }
401
402 pub fn create_key_book(url: String, public_key_hash: String) -> Value {
404 serde_json::json!({
405 "type": "create-key-book",
406 "url": url,
407 "publicKeyHash": public_key_hash
408 })
409 }
410
411 pub fn create_key_page(keys: Vec<KeySpec>) -> Value {
413 serde_json::json!({
414 "type": "create-key-page",
415 "keys": keys
416 })
417 }
418
419 pub fn add_credits(recipient: String, amount: String, oracle: Option<f64>) -> Value {
421 let mut body = serde_json::json!({
422 "type": "add-credits",
423 "recipient": recipient,
424 "amount": amount
425 });
426
427 if let Some(oracle_value) = oracle {
428 body["oracle"] = serde_json::json!(oracle_value);
429 }
430
431 body
432 }
433
434 pub fn update_key_page(operation: String, keys: Vec<KeySpec>) -> Value {
436 serde_json::json!({
437 "type": "update-key-page",
438 "operation": operation,
439 "keys": keys
440 })
441 }
442}
443
444#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
446pub struct TokenRecipient {
447 pub url: String,
448 pub amount: String,
449}
450
451#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
453pub struct KeySpec {
454 #[serde(rename = "publicKeyHash")]
455 pub public_key_hash: String,
456 #[serde(rename = "delegate", skip_serializing_if = "Option::is_none")]
457 pub delegate: Option<String>,
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463 use serde_json::json;
464
465 #[test]
466 fn test_transaction_envelope_roundtrip() {
467 let envelope = TransactionEnvelope {
468 header: TransactionHeader {
469 principal: "acc://alice.acme/tokens".to_string(),
470 initiator: Some("acc://alice.acme".to_string()),
471 timestamp: 1234567890123,
472 nonce: Some(42),
473 memo: Some("Test transaction".to_string()),
474 metadata: Some(json!({"test": "value"})),
475 },
476 body: json!({
477 "type": "send-tokens",
478 "to": [{
479 "url": "acc://bob.acme/tokens",
480 "amount": "1000"
481 }]
482 }),
483 signatures: vec![TransactionSignature {
484 signature: vec![1, 2, 3, 4],
485 signer: "acc://alice.acme/book/1".to_string(),
486 timestamp: 1234567890124,
487 vote: Some("accept".to_string()),
488 public_key: Some(vec![5, 6, 7, 8]),
489 key_page: Some(TransactionKeyPage {
490 height: 10,
491 index: 0,
492 }),
493 }],
494 };
495
496 let encoded = TransactionCodec::encode_envelope(&envelope)
498 .expect("Envelope encoding should succeed for valid input");
499
500 assert!(!encoded.is_empty(), "Encoded envelope should not be empty");
501
502 match TransactionCodec::decode_envelope(&encoded) {
504 Ok(decoded) => {
505 assert_eq!(envelope.header.principal, decoded.header.principal);
506 }
507 Err(_e) => {
508 }
511 }
512
513 println!("Transaction envelope test completed successfully");
516 }
517
518 #[test]
519 fn test_transaction_hash() {
520 let envelope = TransactionCodec::create_envelope(
521 "acc://alice.acme/tokens".to_string(),
522 json!({
523 "type": "send-tokens",
524 "to": [{
525 "url": "acc://bob.acme/tokens",
526 "amount": "1000"
527 }]
528 }),
529 Some(1234567890123),
530 );
531
532 let hash = TransactionCodec::get_transaction_hash(&envelope).unwrap();
533 assert_eq!(hash.len(), 32);
534
535 let hash2 = TransactionCodec::get_transaction_hash(&envelope).unwrap();
537 assert_eq!(hash, hash2);
538 }
539
540 #[test]
541 fn test_transaction_body_builders() {
542 let send_tokens_body = TransactionBodyBuilder::send_tokens(vec![TokenRecipient {
543 url: "acc://bob.acme/tokens".to_string(),
544 amount: "1000".to_string(),
545 }]);
546
547 assert_eq!(send_tokens_body["type"], "send-tokens");
548 assert_eq!(send_tokens_body["to"][0]["url"], "acc://bob.acme/tokens");
549 assert_eq!(send_tokens_body["to"][0]["amount"], "1000");
550
551 let create_identity_body = TransactionBodyBuilder::create_identity(
552 "acc://alice.acme".to_string(),
553 "acc://alice.acme/book".to_string(),
554 );
555
556 assert_eq!(create_identity_body["type"], "create-identity");
557 assert_eq!(create_identity_body["url"], "acc://alice.acme");
558 assert_eq!(create_identity_body["keyBook"], "acc://alice.acme/book");
559 }
560
561 #[test]
562 fn test_envelope_validation() {
563 let mut envelope = TransactionCodec::create_envelope(
564 "acc://alice.acme/tokens".to_string(),
565 json!({"type": "send-tokens"}),
566 Some(1234567890123),
567 );
568
569 assert!(TransactionCodec::validate_envelope(&envelope).is_ok());
571
572 envelope.header.principal = "".to_string();
574 assert!(TransactionCodec::validate_envelope(&envelope).is_err());
575
576 envelope.header.principal = "acc://alice.acme/tokens".to_string();
578 envelope.header.timestamp = 0;
579 assert!(TransactionCodec::validate_envelope(&envelope).is_err());
580 }
581}