1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16#[repr(u8)]
17pub enum FieldType {
18 UInt16 = 1,
20 UInt32 = 2,
22 Amount = 6,
24 Blob = 7,
26 AccountID = 8,
28 Hash256 = 5,
30}
31
32#[must_use]
37pub fn encode_field_id(type_code: u8, field_code: u8) -> Vec<u8> {
38 if type_code < 16 && field_code < 16 {
39 vec![(type_code << 4) | field_code]
40 } else if type_code < 16 {
41 vec![type_code << 4, field_code]
42 } else if field_code < 16 {
43 vec![field_code, type_code]
44 } else {
45 vec![0, type_code, field_code]
46 }
47}
48
49#[must_use]
51pub fn encode_uint32(type_code: u8, field_code: u8, value: u32) -> Vec<u8> {
52 let mut buf = encode_field_id(type_code, field_code);
53 buf.extend_from_slice(&value.to_be_bytes());
54 buf
55}
56
57#[must_use]
59pub fn encode_uint16(type_code: u8, field_code: u8, value: u16) -> Vec<u8> {
60 let mut buf = encode_field_id(type_code, field_code);
61 buf.extend_from_slice(&value.to_be_bytes());
62 buf
63}
64
65#[must_use]
70pub fn encode_xrp_amount(drops: u64) -> Vec<u8> {
71 let encoded = drops | 0x4000_0000_0000_0000;
73 encoded.to_be_bytes().to_vec()
74}
75
76#[must_use]
78pub fn encode_account_id(type_code: u8, field_code: u8, account: &[u8; 20]) -> Vec<u8> {
79 let mut buf = encode_field_id(type_code, field_code);
80 buf.push(20); buf.extend_from_slice(account);
82 buf
83}
84
85#[must_use]
87pub fn encode_blob(type_code: u8, field_code: u8, data: &[u8]) -> Vec<u8> {
88 let mut buf = encode_field_id(type_code, field_code);
89 encode_vl_length(&mut buf, data.len());
90 buf.extend_from_slice(data);
91 buf
92}
93
94fn encode_vl_length(buf: &mut Vec<u8>, len: usize) {
95 if len <= 192 {
96 buf.push(len as u8);
97 } else if len <= 12_480 {
98 let adjusted = len - 193;
99 buf.push((adjusted >> 8) as u8 + 193);
100 buf.push((adjusted & 0xFF) as u8);
101 } else {
102 let adjusted = len - 12_481;
103 buf.push(241u8 + (adjusted >> 16) as u8);
104 buf.push(((adjusted >> 8) & 0xFF) as u8);
105 buf.push((adjusted & 0xFF) as u8);
106 }
107}
108
109mod fields {
115 pub const TRANSACTION_TYPE: (u8, u8) = (1, 2);
117 pub const FLAGS: (u8, u8) = (2, 2);
119 pub const SEQUENCE: (u8, u8) = (2, 4);
120 pub const LAST_LEDGER_SEQUENCE: (u8, u8) = (2, 27);
121 pub const AMOUNT: (u8, u8) = (6, 1);
123 pub const FEE: (u8, u8) = (6, 8);
124 pub const ACCOUNT: (u8, u8) = (8, 1);
126 pub const DESTINATION: (u8, u8) = (8, 3);
127}
128
129pub const TT_PAYMENT: u16 = 0;
131pub const TT_TRUST_SET: u16 = 20;
133
134pub fn serialize_payment(
144 account: &[u8; 20],
145 destination: &[u8; 20],
146 amount_drops: u64,
147 fee_drops: u64,
148 sequence: u32,
149 last_ledger_sequence: u32,
150) -> Vec<u8> {
151 let mut buf = Vec::new();
152
153 buf.extend_from_slice(&encode_uint16(
156 fields::TRANSACTION_TYPE.0,
157 fields::TRANSACTION_TYPE.1,
158 TT_PAYMENT,
159 ));
160 buf.extend_from_slice(&encode_uint32(fields::FLAGS.0, fields::FLAGS.1, 0));
162 buf.extend_from_slice(&encode_uint32(
163 fields::SEQUENCE.0,
164 fields::SEQUENCE.1,
165 sequence,
166 ));
167 buf.extend_from_slice(&encode_uint32(
168 fields::LAST_LEDGER_SEQUENCE.0,
169 fields::LAST_LEDGER_SEQUENCE.1,
170 last_ledger_sequence,
171 ));
172 let mut amount_field = encode_field_id(fields::AMOUNT.0, fields::AMOUNT.1);
174 amount_field.extend_from_slice(&encode_xrp_amount(amount_drops));
175 buf.extend_from_slice(&amount_field);
176
177 let mut fee_field = encode_field_id(fields::FEE.0, fields::FEE.1);
178 fee_field.extend_from_slice(&encode_xrp_amount(fee_drops));
179 buf.extend_from_slice(&fee_field);
180
181 buf.extend_from_slice(&encode_account_id(
183 fields::ACCOUNT.0,
184 fields::ACCOUNT.1,
185 account,
186 ));
187 buf.extend_from_slice(&encode_account_id(
188 fields::DESTINATION.0,
189 fields::DESTINATION.1,
190 destination,
191 ));
192
193 buf
194}
195
196#[derive(Debug, Clone)]
202pub struct IssuedAmount {
203 pub currency: [u8; 20],
205 pub issuer: [u8; 20],
207 pub value: String,
209}
210
211pub fn serialize_trust_set(
220 account: &[u8; 20],
221 limit_amount: &IssuedAmount,
222 fee_drops: u64,
223 sequence: u32,
224 last_ledger_sequence: u32,
225) -> Vec<u8> {
226 let mut buf = Vec::new();
227
228 buf.extend_from_slice(&encode_uint16(
230 fields::TRANSACTION_TYPE.0,
231 fields::TRANSACTION_TYPE.1,
232 TT_TRUST_SET,
233 ));
234 buf.extend_from_slice(&encode_uint32(fields::FLAGS.0, fields::FLAGS.1, 0));
236 buf.extend_from_slice(&encode_uint32(
238 fields::SEQUENCE.0,
239 fields::SEQUENCE.1,
240 sequence,
241 ));
242 buf.extend_from_slice(&encode_uint32(
244 fields::LAST_LEDGER_SEQUENCE.0,
245 fields::LAST_LEDGER_SEQUENCE.1,
246 last_ledger_sequence,
247 ));
248 let mut fee_field = encode_field_id(fields::FEE.0, fields::FEE.1);
250 fee_field.extend_from_slice(&encode_xrp_amount(fee_drops));
251 buf.extend_from_slice(&fee_field);
252 buf.extend_from_slice(&encode_account_id(
254 fields::ACCOUNT.0,
255 fields::ACCOUNT.1,
256 account,
257 ));
258
259 buf.extend_from_slice(&encode_field_id(14, 3)); let encoded_amount = encode_issued_amount(limit_amount);
264 buf.extend_from_slice(&encoded_amount);
265
266 buf
267}
268
269fn encode_issued_amount(amount: &IssuedAmount) -> Vec<u8> {
270 let mut buf = Vec::new();
271 let value_bytes = encode_iou_value(&amount.value);
273 buf.extend_from_slice(&value_bytes);
274 buf.extend_from_slice(&amount.currency);
275 buf.extend_from_slice(&amount.issuer);
276 buf
277}
278
279fn encode_iou_value(value: &str) -> [u8; 8] {
280 let val: f64 = value.parse().unwrap_or(0.0);
283 if val == 0.0 {
284 return 0x8000_0000_0000_0000u64.to_be_bytes();
285 }
286 let mut encoded = 0x8000_0000_0000_0000u64;
288 if val > 0.0 {
289 encoded |= 0x4000_0000_0000_0000;
290 }
291 let abs_val = val.abs();
293 let mantissa = (abs_val * 1_000_000.0) as u64;
294 encoded |= mantissa & 0x003F_FFFF_FFFF_FFFF;
295 encoded.to_be_bytes()
296}
297
298pub const MULTISIGN_PREFIX: [u8; 4] = [0x53, 0x54, 0x58, 0x00];
308
309pub fn multisign_hash(tx_blob: &[u8], signer_account: &[u8; 20]) -> [u8; 32] {
313 use sha2::{Digest, Sha512};
314 let mut hasher = Sha512::new();
315 hasher.update(MULTISIGN_PREFIX);
316 hasher.update(tx_blob);
317 hasher.update(signer_account);
318 let full = hasher.finalize();
319 let mut out = [0u8; 32];
320 out.copy_from_slice(&full[..32]);
321 out
322}
323
324#[derive(Debug, Clone)]
326pub struct SignerEntry {
327 pub account: [u8; 20],
329 pub weight: u16,
331}
332
333pub fn serialize_signer_list(signers: &[SignerEntry], quorum: u32) -> Vec<u8> {
337 let mut buf = Vec::new();
338 buf.extend_from_slice(&quorum.to_be_bytes());
339 for signer in signers {
340 buf.extend_from_slice(&signer.account);
341 buf.extend_from_slice(&signer.weight.to_be_bytes());
342 }
343 buf
344}
345
346#[cfg(test)]
351#[allow(clippy::unwrap_used, clippy::expect_used)]
352mod tests {
353 use super::*;
354
355 #[test]
358 fn test_field_id_compact() {
359 assert_eq!(encode_field_id(1, 2), vec![0x12]);
361 }
362
363 #[test]
364 fn test_field_id_large_field() {
365 assert_eq!(encode_field_id(1, 27), vec![0x10, 27]);
367 }
368
369 #[test]
370 fn test_xrp_amount_encoding() {
371 let drops = 1_000_000u64; let encoded = encode_xrp_amount(drops);
373 assert_eq!(encoded.len(), 8);
374 let val = u64::from_be_bytes(encoded.try_into().unwrap());
376 assert!(val & 0x4000_0000_0000_0000 != 0);
377 }
378
379 #[test]
380 fn test_uint32_encoding() {
381 let encoded = encode_uint32(2, 4, 42);
382 assert_eq!(encoded[0], 0x24); assert_eq!(&encoded[1..5], &42u32.to_be_bytes());
384 }
385
386 #[test]
387 fn test_account_id_encoding() {
388 let account = [0xAA; 20];
389 let encoded = encode_account_id(8, 1, &account);
390 assert_eq!(encoded[0], 0x81); assert_eq!(encoded[1], 20); assert_eq!(&encoded[2..22], &account);
393 }
394
395 #[test]
396 fn test_vl_length_small() {
397 let mut buf = Vec::new();
398 encode_vl_length(&mut buf, 10);
399 assert_eq!(buf, vec![10]);
400 }
401
402 #[test]
403 fn test_vl_length_medium() {
404 let mut buf = Vec::new();
405 encode_vl_length(&mut buf, 200);
406 assert_eq!(buf.len(), 2);
407 }
408
409 #[test]
412 fn test_payment_serialization() {
413 let from = [0xAA; 20];
414 let to = [0xBB; 20];
415 let blob = serialize_payment(&from, &to, 1_000_000, 12, 1, 100);
416 assert!(!blob.is_empty());
417 assert_eq!(blob[0], 0x12);
419 }
420
421 #[test]
422 fn test_payment_different_amount() {
423 let from = [0xAA; 20];
424 let to = [0xBB; 20];
425 let blob1 = serialize_payment(&from, &to, 1_000, 12, 1, 100);
426 let blob2 = serialize_payment(&from, &to, 2_000, 12, 1, 100);
427 assert_ne!(blob1, blob2);
428 }
429
430 #[test]
433 fn test_trust_set_serialization() {
434 let account = [0xAA; 20];
435 let limit = IssuedAmount {
436 currency: {
437 let mut c = [0u8; 20];
438 c[12..15].copy_from_slice(b"USD");
439 c
440 },
441 issuer: [0xBB; 20],
442 value: "100".to_string(),
443 };
444 let blob = serialize_trust_set(&account, &limit, 12, 1, 100);
445 assert!(!blob.is_empty());
446 }
447
448 #[test]
451 fn test_multisign_prefix() {
452 assert_eq!(&MULTISIGN_PREFIX, b"STX\0");
453 }
454
455 #[test]
456 fn test_multisign_hash_deterministic() {
457 let tx_blob = vec![0xAA; 100];
458 let account = [0xBB; 20];
459 let h1 = multisign_hash(&tx_blob, &account);
460 let h2 = multisign_hash(&tx_blob, &account);
461 assert_eq!(h1, h2);
462 }
463
464 #[test]
465 fn test_multisign_hash_different_account() {
466 let tx_blob = vec![0xAA; 100];
467 let h1 = multisign_hash(&tx_blob, &[0xBB; 20]);
468 let h2 = multisign_hash(&tx_blob, &[0xCC; 20]);
469 assert_ne!(h1, h2);
470 }
471
472 #[test]
473 fn test_signer_list() {
474 let signers = vec![
475 SignerEntry {
476 account: [0xAA; 20],
477 weight: 1,
478 },
479 SignerEntry {
480 account: [0xBB; 20],
481 weight: 2,
482 },
483 ];
484 let data = serialize_signer_list(&signers, 3);
485 assert_eq!(data.len(), 48);
487 }
488}