use md_codec::chunk::split;
use md_codec::decode_with_correction;
use md_codec::encode::Descriptor;
use md_codec::origin_path::{OriginPath, PathComponent, PathDecl, PathDeclPaths};
use md_codec::tag::Tag;
use md_codec::tlv::TlvSection;
use md_codec::tree::{Body, Node};
use md_codec::use_site_path::UseSitePath;
const CODEX32_ALPHABET: &[u8; 32] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
fn small_descriptor() -> Descriptor {
Descriptor {
n: 1,
path_decl: PathDecl {
n: 1,
paths: PathDeclPaths::Shared(OriginPath {
components: vec![
PathComponent {
hardened: true,
value: 84,
},
PathComponent {
hardened: true,
value: 0,
},
PathComponent {
hardened: true,
value: 0,
},
],
}),
},
use_site_path: UseSitePath::standard_multipath(),
tree: Node {
tag: Tag::Wpkh,
body: Body::KeyArg { index: 0 },
},
tlv: TlvSection::new_empty(),
}
}
fn corrupt_at(chunk: &str, pos: usize, xor_mask: u8) -> String {
let mut chars: Vec<char> = chunk.chars().collect();
let abs_idx = 3 + pos; let original_sym = CODEX32_ALPHABET
.iter()
.position(|&b| b == chars[abs_idx].to_ascii_lowercase() as u8)
.expect("char in alphabet") as u8;
let new_sym = (original_sym ^ (xor_mask & 0x1F)) & 0x1F;
chars[abs_idx] = CODEX32_ALPHABET[new_sym as usize] as char;
chars.iter().collect()
}
fn extract_corrected_md1(stdout: &str) -> Option<String> {
stdout
.lines()
.filter(|l| !l.starts_with('#') && l.to_ascii_lowercase().starts_with("md1"))
.last()
.map(String::from)
}
#[test]
fn parity_smoke_md_against_toolkit_v0_22_1() {
let home = match std::env::var("HOME") {
Ok(h) => h,
Err(_) => {
eprintln!("parity_smoke: HOME unset; skipping");
return;
}
};
let toolkit_bin = format!("{home}/.cargo/bin/mnemonic");
if !std::path::Path::new(&toolkit_bin).exists() {
eprintln!("parity_smoke: {toolkit_bin} not found; skipping");
return;
}
let version_out = std::process::Command::new(&toolkit_bin)
.arg("--version")
.output();
match version_out {
Ok(out) => {
let v = String::from_utf8_lossy(&out.stdout);
eprintln!("parity_smoke: toolkit binary reports: {}", v.trim());
}
Err(e) => {
eprintln!("parity_smoke: --version failed: {e}; skipping");
return;
}
}
let d = small_descriptor();
let chunks = split(&d).expect("split");
assert_eq!(chunks.len(), 1, "small descriptor must fit in one chunk");
let original = chunks[0].clone();
let bad = corrupt_at(&original, 4, 0b10110);
assert_ne!(bad, original, "corruption changed the chunk");
let (codec_decoded, codec_details) =
decode_with_correction(&[bad.as_str()]).expect("md-codec must decode");
assert_eq!(
codec_decoded, d,
"md-codec correction restores original descriptor"
);
assert_eq!(codec_details.len(), 1, "exactly 1 correction reported");
let codec_correction = &codec_details[0];
let toolkit_out = std::process::Command::new(&toolkit_bin)
.args(["repair", "--md1", &bad])
.output()
.expect("invoke toolkit repair");
let stdout = String::from_utf8_lossy(&toolkit_out.stdout).to_string();
let stderr = String::from_utf8_lossy(&toolkit_out.stderr).to_string();
assert!(
toolkit_out.status.success() || toolkit_out.status.code() == Some(5),
"toolkit repair must succeed (got exit {:?}; stderr: {})",
toolkit_out.status.code(),
stderr
);
let toolkit_corrected = extract_corrected_md1(&stdout)
.unwrap_or_else(|| panic!("could not extract corrected md1 from toolkit stdout:\n{stdout}"));
assert_eq!(
toolkit_corrected.to_lowercase(),
original.to_lowercase(),
"toolkit's corrected output must match md-codec's BCH-corrected codeword"
);
let expected_position_substr = format!("position {}", codec_correction.position);
assert!(
stdout.contains(&expected_position_substr),
"toolkit stdout must cite position {}:\n{stdout}",
codec_correction.position
);
let now_char = codec_correction.now;
assert!(
stdout.contains(&format!("'{now_char}'")),
"toolkit stdout must cite corrected char '{now_char}':\n{stdout}"
);
eprintln!(
"parity_smoke: md-codec @position {} ({} -> {}) == toolkit-v0.22.1 corrected chunk",
codec_correction.position, codec_correction.was, codec_correction.now
);
}