use std::str::FromStr;
use bitcoin::NetworkKind;
use bitcoin::bip32::{ChainCode, ChildNumber, DerivationPath, Fingerprint, Xpub};
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use mk_codec::{Error, KeyCard, decode, encode_with_chunk_set_id};
fn fixture_card() -> KeyCard {
let path = DerivationPath::from_str("48'/0'/0'/2'").unwrap();
let secp = Secp256k1::new();
let sk = SecretKey::from_slice(&[0x42u8; 32]).unwrap();
let pk = PublicKey::from_secret_key(&secp, &sk);
let comps: Vec<ChildNumber> = path.as_ref().to_vec();
let xpub = Xpub {
network: NetworkKind::Main,
depth: comps.len() as u8,
parent_fingerprint: Fingerprint::from([0x10, 0x20, 0x30, 0x40]),
child_number: *comps.last().unwrap(),
public_key: pk,
chain_code: ChainCode::from([0xCCu8; 32]),
};
KeyCard::new(
(0u8..6).map(|i| [i, i, i, i]).collect(),
Some(Fingerprint::from([0xAA, 0xBB, 0xCC, 0xDD])),
path,
xpub,
)
}
#[test]
fn t3a_single_indel_fails_closed() {
let card = fixture_card();
let strings = encode_with_chunk_set_id(&card, 0).unwrap();
let s0 = &strings[0];
let mut chars: Vec<char> = s0.chars().collect();
chars.insert(15, 'p');
let inserted: String = chars.into_iter().collect();
let mut v_ins = strings.clone();
v_ins[0] = inserted;
let parts: Vec<&str> = v_ins.iter().map(String::as_str).collect();
assert!(
decode(&parts).is_err(),
"an inserted symbol must fail closed (never Ok); got {:?}",
decode(&parts)
);
let mut chars: Vec<char> = s0.chars().collect();
chars.remove(15);
let deleted: String = chars.into_iter().collect();
let mut v_del = strings.clone();
v_del[0] = deleted;
let parts: Vec<&str> = v_del.iter().map(String::as_str).collect();
assert!(
decode(&parts).is_err(),
"a deleted symbol must fail closed (never Ok); got {:?}",
decode(&parts)
);
}
#[test]
fn t3b_out_of_band_length_is_invalid_string_length() {
let card = fixture_card();
let strings = encode_with_chunk_set_id(&card, 0).unwrap();
let long = strings.iter().max_by_key(|s| s.chars().count()).unwrap();
let chars: Vec<char> = long.chars().collect();
assert!(
chars.len() > 97,
"fixture chunk too short to trim into the reserved gap; enlarge fixture_card()"
);
let trimmed: String = chars[..97].iter().collect();
assert!(
matches!(
decode(&[trimmed.as_str()]),
Err(Error::InvalidStringLength(94))
),
"reserved-gap length (data-part 94) must be InvalidStringLength(94); got {:?}",
decode(&[trimmed.as_str()])
);
}