1pub mod advanced;
6pub mod transaction;
7
8use crate::crypto;
9use crate::error::SignerError;
10use crate::traits;
11use sha2::{Digest, Sha512};
12use zeroize::Zeroizing;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[must_use]
18pub struct XrpSignature {
19 pub bytes: Vec<u8>,
21}
22
23impl core::fmt::Display for XrpSignature {
24 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
25 write!(f, "0x")?;
26 for byte in &self.bytes {
27 write!(f, "{byte:02x}")?;
28 }
29 Ok(())
30 }
31}
32
33impl XrpSignature {
34 pub fn to_bytes(&self) -> Vec<u8> {
36 self.bytes.clone()
37 }
38
39 pub fn from_bytes(bytes: &[u8]) -> Result<Self, SignerError> {
44 if bytes.is_empty() {
45 return Err(SignerError::InvalidSignature("empty signature".into()));
46 }
47 if bytes[0] == 0x30 {
49 if bytes.len() < 3 || bytes.len() > 73 {
51 return Err(SignerError::InvalidSignature(format!(
52 "invalid DER signature length: {}",
53 bytes.len()
54 )));
55 }
56 } else if bytes.len() != 64 {
57 return Err(SignerError::InvalidSignature(format!(
58 "expected 64-byte Ed25519 or DER ECDSA, got {} bytes starting with 0x{:02x}",
59 bytes.len(),
60 bytes[0]
61 )));
62 }
63 Ok(Self {
64 bytes: bytes.to_vec(),
65 })
66 }
67}
68
69pub fn sha512_half(data: &[u8]) -> [u8; 32] {
71 let full = Sha512::digest(data);
72 let mut out = [0u8; 32];
73 out.copy_from_slice(&full[..32]);
74 out
75}
76
77pub fn account_id(pubkey_bytes: &[u8]) -> [u8; 20] {
79 crypto::hash160(pubkey_bytes)
80}
81
82fn xrp_alphabet() -> Result<bs58::Alphabet, SignerError> {
84 bs58::Alphabet::new(b"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz")
85 .map_err(|e| SignerError::InvalidPublicKey(format!("XRP alphabet: {e}")))
86}
87
88pub fn xrp_address(account_id: &[u8; 20]) -> Result<String, SignerError> {
92 let mut payload = vec![0x00u8]; payload.extend_from_slice(account_id);
94 let checksum = crypto::double_sha256(&payload);
96 payload.extend_from_slice(&checksum[..4]);
97 Ok(bs58::encode(payload)
98 .with_alphabet(&xrp_alphabet()?)
99 .into_string())
100}
101
102pub fn validate_address(address: &str) -> bool {
106 if !address.starts_with('r') {
107 return false;
108 }
109 let alphabet = match xrp_alphabet() {
110 Ok(a) => a,
111 Err(_) => return false,
112 };
113 let decoded = match bs58::decode(address).with_alphabet(&alphabet).into_vec() {
114 Ok(d) => d,
115 Err(_) => return false,
116 };
117 if decoded.len() != 25 || decoded[0] != 0x00 {
118 return false;
119 }
120 use subtle::ConstantTimeEq;
121 let checksum = crypto::double_sha256(&decoded[..21]);
122 checksum[..4].ct_eq(&decoded[21..25]).unwrap_u8() == 1
123}
124
125pub fn encode_x_address(
137 account_id: &[u8; 20],
138 tag: Option<u32>,
139 is_testnet: bool,
140) -> Result<String, SignerError> {
141 let mut payload = Vec::with_capacity(31);
142
143 if is_testnet {
145 payload.extend_from_slice(&[0x05, 0x93]);
146 } else {
147 payload.extend_from_slice(&[0x05, 0x44]);
148 }
149
150 payload.extend_from_slice(account_id);
152
153 match tag {
155 Some(t) => {
156 payload.push(0x01); payload.extend_from_slice(&t.to_le_bytes()); payload.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); }
160 None => {
161 payload.push(0x00); payload.extend_from_slice(&[0x00; 8]); }
164 }
165
166 let checksum = crypto::double_sha256(&payload);
168 payload.extend_from_slice(&checksum[..4]);
169
170 Ok(bs58::encode(payload)
171 .with_alphabet(&xrp_alphabet()?)
172 .into_string())
173}
174
175pub fn decode_x_address(x_address: &str) -> Result<([u8; 20], Option<u32>, bool), SignerError> {
179 let decoded = bs58::decode(x_address)
180 .with_alphabet(&xrp_alphabet()?)
181 .into_vec()
182 .map_err(|_| SignerError::ParseError("invalid X-address Base58".into()))?;
183
184 if decoded.len() != 35 {
185 return Err(SignerError::ParseError(format!(
186 "X-address: expected 35 bytes, got {}",
187 decoded.len()
188 )));
189 }
190
191 let checksum = crypto::double_sha256(&decoded[..31]);
193 if decoded[31..35] != checksum[..4] {
194 return Err(SignerError::ParseError("X-address: bad checksum".into()));
195 }
196
197 let is_testnet = match (decoded[0], decoded[1]) {
199 (0x05, 0x44) => false, (0x05, 0x93) => true, _ => return Err(SignerError::ParseError("X-address: unknown prefix".into())),
202 };
203
204 let mut account = [0u8; 20];
206 account.copy_from_slice(&decoded[2..22]);
207
208 let tag = if decoded[22] == 0x01 {
210 Some(u32::from_le_bytes([
211 decoded[23],
212 decoded[24],
213 decoded[25],
214 decoded[26],
215 ]))
216 } else {
217 None
218 };
219
220 Ok((account, tag, is_testnet))
221}
222
223pub struct XrpEcdsaSigner {
227 signing_key: k256::ecdsa::SigningKey,
228}
229
230impl Drop for XrpEcdsaSigner {
231 fn drop(&mut self) {
232 }
234}
235
236impl XrpEcdsaSigner {
237 pub fn account_id(&self) -> [u8; 20] {
239 account_id(&self.public_key_bytes_inner())
240 }
241
242 pub fn address(&self) -> Result<String, SignerError> {
244 xrp_address(&self.account_id())
245 }
246
247 fn public_key_bytes_inner(&self) -> Vec<u8> {
248 self.signing_key.verifying_key().to_sec1_bytes().to_vec()
249 }
250
251 fn sign_digest(&self, digest: &[u8; 32]) -> Result<XrpSignature, SignerError> {
252 use k256::ecdsa::signature::hazmat::PrehashSigner;
253 let sig: k256::ecdsa::Signature = self
254 .signing_key
255 .sign_prehash(digest)
256 .map_err(|e| SignerError::SigningFailed(e.to_string()))?;
257 Ok(XrpSignature {
258 bytes: sig.to_der().as_bytes().to_vec(),
259 })
260 }
261}
262
263impl traits::Signer for XrpEcdsaSigner {
264 type Signature = XrpSignature;
265 type Error = SignerError;
266
267 fn sign(&self, message: &[u8]) -> Result<XrpSignature, SignerError> {
268 let digest = sha512_half(message);
269 self.sign_digest(&digest)
270 }
271
272 fn sign_prehashed(&self, digest: &[u8]) -> Result<XrpSignature, SignerError> {
273 if digest.len() != 32 {
274 return Err(SignerError::InvalidHashLength {
275 expected: 32,
276 got: digest.len(),
277 });
278 }
279 let mut hash = [0u8; 32];
280 hash.copy_from_slice(digest);
281 self.sign_digest(&hash)
282 }
283
284 fn public_key_bytes(&self) -> Vec<u8> {
285 self.public_key_bytes_inner()
286 }
287
288 fn public_key_bytes_uncompressed(&self) -> Vec<u8> {
289 self.signing_key
290 .verifying_key()
291 .to_encoded_point(false)
292 .as_bytes()
293 .to_vec()
294 }
295}
296
297impl traits::KeyPair for XrpEcdsaSigner {
298 fn generate() -> Result<Self, SignerError> {
299 let mut key_bytes = zeroize::Zeroizing::new([0u8; 32]);
300 crate::security::secure_random(&mut *key_bytes)?;
301 let signing_key = k256::ecdsa::SigningKey::from_bytes((&*key_bytes).into())
302 .map_err(|e| SignerError::InvalidPrivateKey(e.to_string()))?;
303 Ok(Self { signing_key })
304 }
305
306 fn from_bytes(private_key: &[u8]) -> Result<Self, SignerError> {
307 if private_key.len() != 32 {
308 return Err(SignerError::InvalidPrivateKey(format!(
309 "expected 32 bytes, got {}",
310 private_key.len()
311 )));
312 }
313 let signing_key = k256::ecdsa::SigningKey::from_bytes(private_key.into())
314 .map_err(|e| SignerError::InvalidPrivateKey(e.to_string()))?;
315 Ok(Self { signing_key })
316 }
317
318 fn private_key_bytes(&self) -> Zeroizing<Vec<u8>> {
319 Zeroizing::new(self.signing_key.to_bytes().to_vec())
320 }
321}
322
323pub struct XrpEcdsaVerifier {
325 verifying_key: k256::ecdsa::VerifyingKey,
326}
327
328impl XrpEcdsaVerifier {
329 pub fn from_public_key_bytes(bytes: &[u8]) -> Result<Self, SignerError> {
331 let verifying_key = k256::ecdsa::VerifyingKey::from_sec1_bytes(bytes)
332 .map_err(|e| SignerError::InvalidPublicKey(e.to_string()))?;
333 Ok(Self { verifying_key })
334 }
335}
336
337impl traits::Verifier for XrpEcdsaVerifier {
338 type Signature = XrpSignature;
339 type Error = SignerError;
340
341 fn verify(&self, message: &[u8], signature: &XrpSignature) -> Result<bool, SignerError> {
342 let digest = sha512_half(message);
343 self.verify_prehashed(&digest, signature)
344 }
345
346 fn verify_prehashed(
347 &self,
348 digest: &[u8],
349 signature: &XrpSignature,
350 ) -> Result<bool, SignerError> {
351 use k256::ecdsa::signature::hazmat::PrehashVerifier;
352 if digest.len() != 32 {
353 return Err(SignerError::InvalidHashLength {
354 expected: 32,
355 got: digest.len(),
356 });
357 }
358 let sig = k256::ecdsa::Signature::from_der(&signature.bytes)
359 .map_err(|e| SignerError::InvalidSignature(e.to_string()))?;
360 match self.verifying_key.verify_prehash(digest, &sig) {
361 Ok(()) => Ok(true),
362 Err(_) => Ok(false),
363 }
364 }
365}
366
367pub struct XrpEddsaSigner {
371 signing_key: ed25519_dalek::SigningKey,
372}
373
374impl Drop for XrpEddsaSigner {
375 fn drop(&mut self) {
376 }
378}
379
380impl XrpEddsaSigner {
381 pub fn account_id(&self) -> [u8; 20] {
384 let vk = self.signing_key.verifying_key();
385 let mut prefixed = Vec::with_capacity(33);
386 prefixed.push(0xED);
387 prefixed.extend_from_slice(vk.as_bytes());
388 account_id(&prefixed)
389 }
390
391 pub fn address(&self) -> Result<String, SignerError> {
393 xrp_address(&self.account_id())
394 }
395}
396
397impl traits::Signer for XrpEddsaSigner {
398 type Signature = XrpSignature;
399 type Error = SignerError;
400
401 fn sign(&self, message: &[u8]) -> Result<XrpSignature, SignerError> {
402 use ed25519_dalek::Signer as DalekSigner;
403 let sig = DalekSigner::sign(&self.signing_key, message);
404 Ok(XrpSignature {
405 bytes: sig.to_bytes().to_vec(),
406 })
407 }
408
409 fn sign_prehashed(&self, digest: &[u8]) -> Result<XrpSignature, SignerError> {
413 self.sign(digest)
416 }
417
418 fn public_key_bytes(&self) -> Vec<u8> {
419 self.signing_key.verifying_key().as_bytes().to_vec()
420 }
421
422 fn public_key_bytes_uncompressed(&self) -> Vec<u8> {
423 self.public_key_bytes()
425 }
426}
427
428impl traits::KeyPair for XrpEddsaSigner {
429 fn generate() -> Result<Self, SignerError> {
430 let mut key_bytes = zeroize::Zeroizing::new([0u8; 32]);
431 crate::security::secure_random(&mut *key_bytes)?;
432 let signing_key = ed25519_dalek::SigningKey::from_bytes(&key_bytes);
433 Ok(Self { signing_key })
434 }
435
436 fn from_bytes(private_key: &[u8]) -> Result<Self, SignerError> {
437 if private_key.len() != 32 {
438 return Err(SignerError::InvalidPrivateKey(format!(
439 "expected 32 bytes, got {}",
440 private_key.len()
441 )));
442 }
443 let mut bytes = [0u8; 32];
444 bytes.copy_from_slice(private_key);
445 let signing_key = ed25519_dalek::SigningKey::from_bytes(&bytes);
446 Ok(Self { signing_key })
447 }
448
449 fn private_key_bytes(&self) -> Zeroizing<Vec<u8>> {
450 Zeroizing::new(self.signing_key.to_bytes().to_vec())
451 }
452}
453
454pub struct XrpEddsaVerifier {
456 verifying_key: ed25519_dalek::VerifyingKey,
457}
458
459impl XrpEddsaVerifier {
460 pub fn from_public_key_bytes(bytes: &[u8]) -> Result<Self, SignerError> {
462 if bytes.len() != 32 {
463 return Err(SignerError::InvalidPublicKey(format!(
464 "expected 32 bytes, got {}",
465 bytes.len()
466 )));
467 }
468 let mut pk_bytes = [0u8; 32];
469 pk_bytes.copy_from_slice(bytes);
470 let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&pk_bytes)
471 .map_err(|e| SignerError::InvalidPublicKey(e.to_string()))?;
472 Ok(Self { verifying_key })
473 }
474}
475
476impl traits::Verifier for XrpEddsaVerifier {
477 type Signature = XrpSignature;
478 type Error = SignerError;
479
480 fn verify(&self, message: &[u8], signature: &XrpSignature) -> Result<bool, SignerError> {
481 self.verify_prehashed(message, signature)
482 }
483
484 fn verify_prehashed(
485 &self,
486 digest: &[u8],
487 signature: &XrpSignature,
488 ) -> Result<bool, SignerError> {
489 use ed25519_dalek::Verifier as DalekVerifier;
490 if signature.bytes.len() != 64 {
491 return Err(SignerError::InvalidSignature(format!(
492 "expected 64 bytes, got {}",
493 signature.bytes.len()
494 )));
495 }
496 let mut sig_bytes = [0u8; 64];
497 sig_bytes.copy_from_slice(&signature.bytes);
498 let sig = ed25519_dalek::Signature::from_bytes(&sig_bytes);
499 match DalekVerifier::verify(&self.verifying_key, digest, &sig) {
500 Ok(()) => Ok(true),
501 Err(_) => Ok(false),
502 }
503 }
504}
505
506#[cfg(test)]
507#[allow(clippy::unwrap_used, clippy::expect_used)]
508mod tests {
509 use super::*;
510 use crate::traits::{KeyPair, Signer, Verifier};
511
512 #[test]
513 fn test_ecdsa_generate() {
514 let signer = XrpEcdsaSigner::generate().unwrap();
515 assert_eq!(signer.public_key_bytes().len(), 33);
516 }
517
518 #[test]
519 fn test_eddsa_generate() {
520 let signer = XrpEddsaSigner::generate().unwrap();
521 assert_eq!(signer.public_key_bytes().len(), 32);
522 }
523
524 #[test]
525 fn test_ecdsa_sign_verify() {
526 let signer = XrpEcdsaSigner::generate().unwrap();
527 let sig = signer.sign(b"hello xrp").unwrap();
528 let verifier = XrpEcdsaVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
529 assert!(verifier.verify(b"hello xrp", &sig).unwrap());
530 }
531
532 #[test]
533 fn test_eddsa_sign_verify() {
534 let signer = XrpEddsaSigner::generate().unwrap();
535 let sig = signer.sign(b"hello xrp ed25519").unwrap();
536 let verifier = XrpEddsaVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
537 assert!(verifier.verify(b"hello xrp ed25519", &sig).unwrap());
538 }
539
540 #[test]
541 fn test_sha512_half() {
542 let result = sha512_half(b"hello");
543 assert_eq!(result.len(), 32);
544 let full = Sha512::digest(b"hello");
546 assert_eq!(&result[..], &full[..32]);
547 }
548
549 #[test]
550 fn test_account_id_ecdsa() {
551 let signer = XrpEcdsaSigner::generate().unwrap();
552 let id = signer.account_id();
553 assert_eq!(id.len(), 20);
554 }
555
556 #[test]
557 fn test_account_id_eddsa() {
558 let signer = XrpEddsaSigner::generate().unwrap();
559 let id = signer.account_id();
560 assert_eq!(id.len(), 20);
561 }
562
563 #[test]
564 fn test_invalid_key_rejected() {
565 assert!(XrpEcdsaSigner::from_bytes(&[0u8; 32]).is_err());
566 assert!(XrpEcdsaSigner::from_bytes(&[1u8; 31]).is_err());
567 assert!(XrpEddsaSigner::from_bytes(&[1u8; 31]).is_err());
568 }
569
570 #[test]
571 fn test_tampered_sig_fails_ecdsa() {
572 let signer = XrpEcdsaSigner::generate().unwrap();
573 let sig = signer.sign(b"tamper").unwrap();
574 let verifier = XrpEcdsaVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
575 let mut tampered = sig.clone();
576 if let Some(b) = tampered.bytes.last_mut() {
577 *b ^= 0xff;
578 }
579 let result = verifier.verify(b"tamper", &tampered);
580 assert!(result.is_err() || !result.unwrap());
581 }
582
583 #[test]
584 fn test_tampered_sig_fails_eddsa() {
585 let signer = XrpEddsaSigner::generate().unwrap();
586 let sig = signer.sign(b"tamper").unwrap();
587 let verifier = XrpEddsaVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
588 let mut tampered = sig.clone();
589 tampered.bytes[0] ^= 0xff;
590 let result = verifier.verify(b"tamper", &tampered);
591 assert!(result.is_err() || !result.unwrap());
592 }
593
594 #[test]
595 fn test_sign_prehashed_ecdsa() {
596 let signer = XrpEcdsaSigner::generate().unwrap();
597 let msg = b"prehash test";
598 let digest = sha512_half(msg);
599 let sig = signer.sign_prehashed(&digest).unwrap();
600 let verifier = XrpEcdsaVerifier::from_public_key_bytes(&signer.public_key_bytes()).unwrap();
601 assert!(verifier.verify_prehashed(&digest, &sig).unwrap());
602 }
603
604 #[test]
605 fn test_zeroize_on_drop_ecdsa() {
606 let signer = XrpEcdsaSigner::generate().unwrap();
607 let _: Zeroizing<Vec<u8>> = signer.private_key_bytes();
608 drop(signer);
609 }
610
611 #[test]
612 fn test_zeroize_on_drop_eddsa() {
613 let signer = XrpEddsaSigner::generate().unwrap();
614 let _: Zeroizing<Vec<u8>> = signer.private_key_bytes();
615 drop(signer);
616 }
617
618 #[test]
620 fn test_rfc8032_vector_xrp_eddsa() {
621 let sk = hex::decode("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")
622 .unwrap();
623 let expected_sig = hex::decode(
624 "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"
625 ).unwrap();
626
627 let signer = XrpEddsaSigner::from_bytes(&sk).unwrap();
628 let sig = signer.sign(b"").unwrap(); assert_eq!(sig.bytes, expected_sig);
630 }
631
632 #[test]
635 fn test_xrp_ecdsa_address_format() {
636 let signer = XrpEcdsaSigner::generate().unwrap();
637 let addr = signer.address().unwrap();
638 assert!(addr.starts_with('r'), "XRP address must start with 'r'");
639 assert!(addr.len() >= 25 && addr.len() <= 35);
640 assert!(validate_address(&addr));
641 }
642
643 #[test]
644 fn test_xrp_eddsa_address_format() {
645 let signer = XrpEddsaSigner::generate().unwrap();
646 let addr = signer.address().unwrap();
647 assert!(addr.starts_with('r'));
648 assert!(validate_address(&addr));
649 }
650
651 #[test]
652 fn test_xrp_address_validation_edges() {
653 assert!(!validate_address(""));
654 assert!(!validate_address("1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH")); assert!(!validate_address("rINVALID")); }
657
658 #[test]
659 fn test_sha512_half_deterministic() {
660 let h1 = sha512_half(b"test");
661 let h2 = sha512_half(b"test");
662 assert_eq!(h1, h2);
663 assert_eq!(h1.len(), 32);
664 }
665
666 #[test]
669 fn test_x_address_roundtrip_no_tag() {
670 let account = [0xAA; 20];
671 let x_addr = encode_x_address(&account, None, false).unwrap();
672 let (decoded_acct, tag, testnet) = decode_x_address(&x_addr).unwrap();
673 assert_eq!(decoded_acct, account);
674 assert!(tag.is_none());
675 assert!(!testnet);
676 }
677
678 #[test]
679 fn test_x_address_roundtrip_with_tag() {
680 let account = [0xBB; 20];
681 let x_addr = encode_x_address(&account, Some(12345), false).unwrap();
682 let (decoded_acct, tag, testnet) = decode_x_address(&x_addr).unwrap();
683 assert_eq!(decoded_acct, account);
684 assert_eq!(tag, Some(12345));
685 assert!(!testnet);
686 }
687
688 #[test]
689 fn test_x_address_testnet() {
690 let account = [0xCC; 20];
691 let x_addr = encode_x_address(&account, None, true).unwrap();
692 let (_, _, testnet) = decode_x_address(&x_addr).unwrap();
693 assert!(testnet);
694 }
695
696 #[test]
697 fn test_x_address_mainnet_vs_testnet() {
698 let account = [0xDD; 20];
699 let main = encode_x_address(&account, None, false).unwrap();
700 let test = encode_x_address(&account, None, true).unwrap();
701 assert_ne!(main, test);
702 }
703
704 #[test]
705 fn test_x_address_from_ecdsa_signer() {
706 let signer = XrpEcdsaSigner::generate().unwrap();
707 let acct_id = signer.account_id();
708 let x_addr = encode_x_address(&acct_id, Some(42), false).unwrap();
709 let (decoded_acct, tag, _) = decode_x_address(&x_addr).unwrap();
710 assert_eq!(decoded_acct, acct_id);
711 assert_eq!(tag, Some(42));
712 }
713
714 #[test]
721 fn test_xrp_classic_address_known_vector() {
722 let account_id = hex::decode("b5f762798a53d543a014caf8b297cff8f2f937e8").unwrap();
723 let mut acct = [0u8; 20];
724 acct.copy_from_slice(&account_id);
725 let addr = xrp_address(&acct).unwrap();
726 assert_eq!(
727 addr, "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
728 "Classic address must match xrpl.org Genesis Account"
729 );
730 assert!(validate_address(&addr));
731 }
732
733 #[test]
734 fn test_xrp_x_address_known_vector_no_tag() {
735 let account_id = hex::decode("b5f762798a53d543a014caf8b297cff8f2f937e8").unwrap();
736 let mut acct = [0u8; 20];
737 acct.copy_from_slice(&account_id);
738
739 let x_addr = encode_x_address(&acct, None, false).unwrap();
741
742 assert!(
744 x_addr.starts_with('X'),
745 "mainnet X-address must start with X"
746 );
747
748 let (decoded_acct, tag, is_testnet) = decode_x_address(&x_addr).unwrap();
750 assert_eq!(decoded_acct, acct, "account ID must survive roundtrip");
751 assert!(tag.is_none(), "no-tag must decode as None");
752 assert!(!is_testnet, "mainnet flag must survive roundtrip");
753 }
754
755 #[test]
756 fn test_xrp_x_address_roundtrip_with_known_acct() {
757 let account_id = hex::decode("b5f762798a53d543a014caf8b297cff8f2f937e8").unwrap();
758 let mut acct = [0u8; 20];
759 acct.copy_from_slice(&account_id);
760
761 let x_addr = encode_x_address(&acct, Some(12345), false).unwrap();
763 let (decoded_acct, tag, is_testnet) = decode_x_address(&x_addr).unwrap();
764 assert_eq!(decoded_acct, acct);
765 assert_eq!(tag, Some(12345));
766 assert!(!is_testnet);
767 }
768
769 #[test]
770 fn test_xrp_x_address_decode_invalid() {
771 assert!(decode_x_address("X7Acg").is_err());
773 assert!(decode_x_address("XXXXXXXXXXX").is_err());
775 }
776}