use crate::{p2sh_hash, Registry};
use std::ops::Range;
pub const TOKEN_TEMPLATE: &str = "KCC20 token";
pub const MINTER_TEMPLATE: &str = "KCC20 minter";
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TokenState {
pub owner: [u8; 32],
pub identifier_type: u8,
pub amount_raw: Vec<u8>,
pub minter_raw: Vec<u8>,
}
impl TokenState {
pub fn amount_i64(&self) -> Option<i64> {
let bytes: [u8; 8] = self.amount_raw.as_slice().try_into().ok()?;
let v = i64::from_le_bytes(bytes);
(v >= 0).then_some(v)
}
pub fn mints(&self) -> Option<bool> {
if self.minter_raw.is_empty() {
return Some(false);
}
self.is_minter()
}
pub fn is_minter(&self) -> Option<bool> {
match self.minter_raw.as_slice() {
[0x00] => Some(false),
[0x01] => Some(true),
_ => None,
}
}
pub fn owner_key(&self) -> String {
let mut bytes = Vec::with_capacity(33);
bytes.push(self.identifier_type);
bytes.extend_from_slice(&self.owner);
hex::encode(bytes)
}
}
pub fn revealed_template(
registry: &Registry,
spk_version: u16,
program: &[u8],
) -> Option<&'static str> {
registry
.decode(spk_version, program)
.template
.or_else(|| locate_state_block(program).map(|_| TOKEN_TEMPLATE))
.or_else(|| {
crate::kcc0020::pinned_program_state(program)
.map(|(pin, _)| pin.family_label())
})
}
pub struct CellFamily {
pub template: &'static str,
pub owner: &'static str,
pub identifier_type: &'static str,
pub amount: &'static str,
pub is_minter: Option<&'static str>,
pub mode: Option<&'static str>,
}
pub const CELL_FAMILIES: &[CellFamily] = &[
CellFamily {
template: TOKEN_TEMPLATE,
owner: "owner_identifier",
identifier_type: "identifier_type",
amount: "amount",
is_minter: Some("is_minter"),
mode: None,
},
CellFamily {
template: "KaspaCom · token",
owner: "owner_identifier",
identifier_type: "identifier_type",
amount: "amount",
is_minter: None,
mode: Some("mint_mode"),
},
CellFamily {
template: "Zealous · token cell",
owner: "owner_identifier",
identifier_type: "identifier_type",
amount: "amount",
is_minter: None,
mode: None,
},
CellFamily {
template: "KForge · token cell",
owner: "owner_identifier",
identifier_type: "identifier_type",
amount: "amount",
is_minter: Some("is_minter"),
mode: None,
},
];
pub fn cell_template_names() -> Vec<&'static str> {
let mut names: Vec<&'static str> = CELL_FAMILIES.iter().map(|f| f.template).collect();
names.extend(crate::kcc0020::spec_template_names());
names
}
pub fn is_cell_template(name: &str) -> bool {
CELL_FAMILIES.iter().any(|f| f.template == name) || crate::kcc0020::is_spec_template(name)
}
fn scheme_byte(raw: &[u8]) -> Option<u8> {
let (first, rest) = raw.split_first()?;
rest.iter().all(|b| *b == 0).then_some(*first)
}
pub fn decode_token_state(
registry: &Registry,
spk_version: u16,
program: &[u8],
) -> Option<TokenState> {
let d = registry.decode(spk_version, program);
if let Some(fam) = d
.template
.and_then(|t| CELL_FAMILIES.iter().find(|f| f.template == t))
{
let field = |name: &str| {
d.fields
.iter()
.find(|f| f.name == name)
.map(|f| f.value.clone())
};
let owner: [u8; 32] = field(fam.owner)?.try_into().ok()?;
let identifier_type = scheme_byte(&field(fam.identifier_type)?)?;
return Some(TokenState {
owner,
identifier_type,
amount_raw: field(fam.amount)?,
minter_raw: match fam.is_minter {
Some(label) => field(label)?,
None => Vec::new(),
},
});
}
decode_state_block(program)
}
pub fn decode_state_block(program: &[u8]) -> Option<TokenState> {
let b = locate_state_block(program)?;
Some(TokenState {
owner: program.get(b.owner())?.try_into().ok()?,
identifier_type: *program.get(b.identifier_type())?,
amount_raw: program.get(b.amount())?.to_vec(),
minter_raw: vec![*program.get(b.is_minter())?],
})
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct StateBlock {
pub start: usize,
pub guarded: bool,
}
impl StateBlock {
pub fn owner(&self) -> std::ops::Range<usize> {
self.start + 1..self.start + 33
}
pub fn identifier_type(&self) -> usize {
self.start + 34
}
pub fn amount(&self) -> std::ops::Range<usize> {
self.start + 36..self.start + 44
}
pub fn is_minter(&self) -> usize {
self.start + 45
}
pub fn end(&self) -> usize {
self.start + 46
}
}
pub fn locate_state_block(program: &[u8]) -> Option<StateBlock> {
if program.len() >= 48
&& program[0] == 0x6b
&& program[1] == 0x20
&& program[34] == 0x01
&& program[36] == 0x08
&& program[45] == 0x01
&& program[47] == 0x6c
{
return Some(StateBlock {
start: 1,
guarded: true,
});
}
if program.len() >= 46
&& program[0] == 0x20
&& program[33] == 0x01
&& program[35] == 0x08
&& program[44] == 0x01
{
return Some(StateBlock {
start: 0,
guarded: false,
});
}
None
}
pub fn has_state_block(program: &[u8]) -> bool {
locate_state_block(program).is_some()
}
pub fn kcc1_template_parts(program: &[u8]) -> Option<(&[u8], &[u8])> {
let b = locate_state_block(program)?;
Some((&program[..b.start], &program[b.end()..]))
}
pub fn kcc1_template_hash(program: &[u8]) -> Option<[u8; 32]> {
let (prefix, suffix) = kcc1_template_parts(program)?;
Some(crate::kcc1::template_hash(prefix, suffix))
}
pub fn kcc1_template_hash_55b28d8(program: &[u8]) -> Option<[u8; 32]> {
let (prefix, suffix) = kcc1_template_parts(program)?;
Some(crate::argent::template_hash(prefix, suffix))
}
pub fn splice_token_state(
program: &[u8],
owner: &[u8; 32],
identifier_type: u8,
amount: &[u8; 8],
is_minter: u8,
) -> Option<Vec<u8>> {
let b = locate_state_block(program)?;
let mut p = program.to_vec();
p[b.owner()].copy_from_slice(owner);
p[b.identifier_type()] = identifier_type;
p[b.amount()].copy_from_slice(amount);
p[b.is_minter()] = is_minter;
Some(p)
}
fn family_slot_ranges(
template: &str,
program: &[u8],
) -> Option<Vec<(&'static str, Range<usize>)>> {
cell_skeletons()
.iter()
.filter(|s| s.name == template)
.find_map(|s| s.slot_ranges(program))
}
type SlotRanges = Vec<(&'static str, Range<usize>)>;
fn family_layout(
registry: &Registry,
program: &[u8],
) -> Option<(&'static CellFamily, SlotRanges)> {
let template = registry.decode(0, program).template?;
let fam = CELL_FAMILIES.iter().find(|f| f.template == template)?;
let ranges = family_slot_ranges(template, program)?;
Some((fam, ranges))
}
fn write_slot(out: &mut [u8], range: &Range<usize>, src: &[u8]) -> bool {
let width = range.len();
if src.len() > width && src[width..].iter().any(|b| *b != 0) {
return false;
}
let n = src.len().min(width);
out[range.start..range.start + n].copy_from_slice(&src[..n]);
for b in &mut out[range.start + n..range.end] {
*b = 0;
}
true
}
fn splice_family_state(
registry: &Registry,
program: &[u8],
owner: &[u8; 32],
identifier_type: u8,
amount: &[u8; 8],
mode: Option<u64>,
) -> Option<Vec<u8>> {
let (fam, ranges) = family_layout(registry, program)?;
splice_into(fam, &ranges, program, owner, identifier_type, amount, mode)
}
fn splice_into(
fam: &CellFamily,
ranges: &[(&'static str, Range<usize>)],
program: &[u8],
owner: &[u8; 32],
identifier_type: u8,
amount: &[u8; 8],
mode: Option<u64>,
) -> Option<Vec<u8>> {
let mode_bytes = mode.map(u64::to_le_bytes);
let mut p = program.to_vec();
let mut wrote_owner = false;
let mut wrote_amount = false;
for (label, range) in ranges {
let src: &[u8] = if *label == fam.owner {
wrote_owner = true;
owner
} else if *label == fam.identifier_type {
&[identifier_type]
} else if *label == fam.amount {
wrote_amount = true;
amount
} else if fam.mode == Some(*label) {
match &mode_bytes {
Some(m) => m,
None => continue,
}
} else {
continue;
};
if !write_slot(&mut p, range, src) {
return None;
}
}
(wrote_owner && wrote_amount).then_some(p)
}
pub struct CellSplicer {
base: Vec<u8>,
owner: Range<usize>,
identifier_type: Range<usize>,
amount: Range<usize>,
is_minter: Option<Range<usize>>,
}
impl CellSplicer {
pub fn new(registry: &Registry, base: &[u8]) -> Option<Self> {
if let Some(b) = locate_state_block(base) {
return Some(Self {
base: base.to_vec(),
owner: b.owner(),
identifier_type: b.identifier_type()..b.identifier_type() + 1,
amount: b.amount(),
is_minter: Some(b.is_minter()..b.is_minter() + 1),
});
}
let (fam, ranges) = family_layout(registry, base)?;
let pick = |label: &str| {
ranges
.iter()
.find(|(l, _)| *l == label)
.map(|(_, r)| r.clone())
};
Some(Self {
base: base.to_vec(),
owner: pick(fam.owner)?,
identifier_type: pick(fam.identifier_type)?,
amount: pick(fam.amount)?,
is_minter: fam.is_minter.and_then(pick),
})
}
pub fn has_minter_slot(&self) -> bool {
self.is_minter.is_some()
}
pub fn prove(
&self,
output_spk: &[u8],
owner: &[u8; 32],
identifier_type: u8,
amount: &[u8; 8],
is_minter: u8,
) -> Option<TokenState> {
let want = p2sh_hash(output_spk)?;
let mut p = self.base.clone();
if !write_slot(&mut p, &self.owner, owner)
|| !write_slot(&mut p, &self.identifier_type, &[identifier_type])
|| !write_slot(&mut p, &self.amount, amount)
{
return None;
}
if let Some(r) = &self.is_minter {
if !write_slot(&mut p, r, &[is_minter]) {
return None;
}
}
(blake2b_256(&p) == want).then(|| TokenState {
owner: *owner,
identifier_type,
amount_raw: amount.to_vec(),
minter_raw: match &self.is_minter {
Some(_) => vec![is_minter],
None => Vec::new(),
},
})
}
pub fn program_for(
&self,
owner: &[u8; 32],
identifier_type: u8,
amount: &[u8; 8],
is_minter: u8,
) -> Option<Vec<u8>> {
let mut p = self.base.clone();
write_slot(&mut p, &self.owner, owner).then_some(())?;
write_slot(&mut p, &self.identifier_type, &[identifier_type]).then_some(())?;
write_slot(&mut p, &self.amount, amount).then_some(())?;
if let Some(r) = &self.is_minter {
write_slot(&mut p, r, &[is_minter]).then_some(())?;
}
Some(p)
}
}
fn cell_skeletons() -> &'static [crate::Skeleton] {
static SKELETONS: std::sync::OnceLock<Vec<crate::Skeleton>> = std::sync::OnceLock::new();
SKELETONS.get_or_init(|| {
crate::observed::observed_skeletons()
.into_iter()
.chain(crate::x402_skeletons())
.collect()
})
}
pub fn canonical_cell_bases(registry: &Registry, program: &[u8]) -> Vec<Vec<u8>> {
if has_state_block(program) {
return splice_token_state(program, &[0u8; 32], 0, &[0u8; 8], 0)
.into_iter()
.collect();
}
let Some((fam, ranges)) = family_layout(registry, program) else {
return Vec::new();
};
let zeroed = |mode: Option<u64>| {
splice_into(fam, &ranges, program, &[0u8; 32], 0, &[0u8; 8], mode)
};
let Some(kept) = zeroed(None) else {
return Vec::new();
};
let mut bases = vec![kept];
let mode_is_set = ranges.iter().any(|(label, range)| {
fam.mode == Some(*label)
&& program
.get(range.clone())
.is_some_and(|slot| slot.iter().any(|b| *b != 0))
});
if mode_is_set {
bases.extend(zeroed(Some(0)));
}
bases
}
pub fn canonical_cell_base(registry: &Registry, program: &[u8]) -> Option<Vec<u8>> {
canonical_cell_bases(registry, program).into_iter().next()
}
pub fn is_spliceable_cell(registry: &Registry, program: &[u8]) -> bool {
has_state_block(program)
|| registry
.decode(0, program)
.template
.is_some_and(|t| CELL_FAMILIES.iter().any(|f| f.template == t))
}
pub fn prove_output_state_any(
registry: &Registry,
base_program: &[u8],
output_spk: &[u8],
owner: &[u8; 32],
identifier_type: u8,
amount: &[u8; 8],
is_minter: u8,
) -> Option<(TokenState, Vec<u8>)> {
if let Some(st) = prove_output_state(
base_program,
output_spk,
owner,
identifier_type,
amount,
is_minter,
) {
let program = splice_token_state(base_program, owner, identifier_type, amount, is_minter)?;
return Some((st, program));
}
let want = p2sh_hash(output_spk)?;
let candidate =
splice_family_state(registry, base_program, owner, identifier_type, amount, None)?;
(blake2b_256(&candidate) == want).then(|| (TokenState {
owner: *owner,
identifier_type,
amount_raw: amount.to_vec(),
minter_raw: Vec::new(),
}, candidate))
}
pub fn blake2b_256(bytes: &[u8]) -> [u8; 32] {
let mut out = [0u8; 32];
out.copy_from_slice(
blake2b_simd::Params::new()
.hash_length(32)
.hash(bytes)
.as_bytes(),
);
out
}
pub fn prove_output_state(
base_program: &[u8],
output_spk: &[u8],
owner: &[u8; 32],
identifier_type: u8,
amount: &[u8; 8],
is_minter: u8,
) -> Option<TokenState> {
let want = p2sh_hash(output_spk)?;
let candidate = splice_token_state(base_program, owner, identifier_type, amount, is_minter)?;
(blake2b_256(&candidate) == want).then(|| TokenState {
owner: *owner,
identifier_type,
amount_raw: amount.to_vec(),
minter_raw: vec![is_minter],
})
}
#[cfg(test)]
mod tests {
use super::*;
fn builds() -> [&'static [u8]; 3] {
[
include_bytes!("../fixtures/kcc20_a_a.bin").as_slice(),
include_bytes!("../fixtures/kcc20_b_a.bin").as_slice(),
include_bytes!("../fixtures/kcc20_c_a.bin").as_slice(),
]
}
const UNGUARDED_KRON: &[u8] = include_bytes!("../fixtures/kcc20_unguarded_kron.bin");
#[test]
fn locates_the_state_block_in_both_builds() {
for base in builds() {
let b = locate_state_block(base).expect("guarded build must locate");
assert_eq!(
b,
StateBlock {
start: 1,
guarded: true
}
);
}
let b = locate_state_block(UNGUARDED_KRON).expect("unguarded build must locate");
assert_eq!(
b,
StateBlock {
start: 0,
guarded: false
}
);
assert_eq!(UNGUARDED_KRON.len(), 2433);
assert_ne!(
UNGUARDED_KRON[0], 0x6b,
"this fixture exists because it is not guarded"
);
}
#[test]
fn decodes_the_unguarded_build_without_a_registered_skeleton() {
let registry = Registry::default();
assert_ne!(
registry.decode(1, UNGUARDED_KRON).template,
Some(TOKEN_TEMPLATE)
);
let st = decode_token_state(®istry, 1, UNGUARDED_KRON)
.expect("an unregistered build must still decode from its located block");
assert_eq!(
hex::encode(st.owner),
"005f70b2d4ca0ff5b9106778a24e5c3551f1e36a61399faadd2a68de592132a0"
);
assert_eq!(st.identifier_type, 3);
assert_eq!(st.amount_i64(), Some(5_000_000));
assert_eq!(st.is_minter(), Some(false));
}
#[test]
fn unguarded_build_splices_proves_and_hashes_apart_from_guarded() {
let owner = [0x5au8; 32];
let amount = 123_456i64.to_le_bytes();
let spliced = splice_token_state(UNGUARDED_KRON, &owner, 2, &amount, 1).unwrap();
assert_eq!(spliced.len(), UNGUARDED_KRON.len());
let st = decode_state_block(&spliced).expect("spliced unguarded program must decode");
assert_eq!(st.owner, owner);
assert_eq!(st.identifier_type, 2);
assert_eq!(st.amount_i64(), Some(123_456));
assert_eq!(st.is_minter(), Some(true));
let guarded_hash = kcc1_template_hash(builds()[0]).unwrap();
let unguarded_hash = kcc1_template_hash(UNGUARDED_KRON).unwrap();
assert_ne!(guarded_hash, unguarded_hash);
assert_eq!(kcc1_template_hash(&spliced).unwrap(), unguarded_hash);
}
#[test]
fn splice_then_decode_roundtrips_on_all_builds() {
let registry = Registry::default();
for base in builds() {
assert!(has_state_block(base));
let owner = [0xabu8; 32];
let amount = 71_753i64.to_le_bytes();
for (id_type, minter) in [(0x00u8, 0x00u8), (0x02, 0x01)] {
let spliced = splice_token_state(base, &owner, id_type, &amount, minter).unwrap();
let st = decode_token_state(®istry, 1, &spliced)
.expect("spliced program must still decode as KCC20 token");
assert_eq!(st.owner, owner);
assert_eq!(st.identifier_type, id_type);
assert_eq!(st.amount_i64(), Some(71_753));
assert_eq!(st.is_minter(), Some(minter == 1));
}
}
}
#[test]
fn prove_output_state_accepts_only_the_committed_state() {
let base = builds()[0];
let owner = [0x11u8; 32];
let amount = 4_000i64.to_le_bytes();
let committed = splice_token_state(base, &owner, 0x00, &amount, 0x00).unwrap();
let mut spk = vec![0xaa, 0x20];
spk.extend_from_slice(&blake2b_256(&committed));
spk.push(0x87);
let st = prove_output_state(base, &spk, &owner, 0x00, &amount, 0x00).unwrap();
assert_eq!(st.amount_i64(), Some(4_000));
assert!(prove_output_state(base, &spk, &owner, 0x02, &amount, 0x00).is_none());
let wrong_amount = 4_001i64.to_le_bytes();
assert!(prove_output_state(base, &spk, &owner, 0x00, &wrong_amount, 0x00).is_none());
assert!(prove_output_state(builds()[1], &spk, &owner, 0x00, &amount, 0x00).is_none());
}
#[test]
fn amount_strictness() {
let mk = |raw: &[u8]| TokenState {
owner: [0; 32],
identifier_type: 0,
amount_raw: raw.to_vec(),
minter_raw: vec![0],
};
assert_eq!(mk(&i64::MAX.to_le_bytes()).amount_i64(), Some(i64::MAX));
assert_eq!(mk(&0i64.to_le_bytes()).amount_i64(), Some(0));
assert_eq!(mk(&[0, 0, 0, 0, 0, 0, 0, 0x80]).amount_i64(), None);
assert_eq!(mk(&[1, 0, 0, 0]).amount_i64(), None);
assert_eq!(mk(&[]).amount_i64(), None);
let mut st = mk(&1i64.to_le_bytes());
st.minter_raw = vec![2];
assert_eq!(st.is_minter(), None);
st.minter_raw = vec![];
assert_eq!(st.is_minter(), None);
}
}
#[cfg(test)]
mod cell_family_tests {
use super::*;
#[test]
fn a_kaspacom_cell_decodes_to_token_state() {
let reg = Registry::default();
let program = include_bytes!("../fixtures/kcom_token_b.bin").as_slice();
assert_eq!(
revealed_template(®, 0, program),
Some("KaspaCom · token"),
"the family must be recognised before it can be decoded"
);
let st = decode_token_state(®, 0, program).expect("must decode to token state");
assert_eq!(st.owner.len(), 32);
assert!(matches!(st.identifier_type, 0..=3));
assert_eq!(
u64::from_le_bytes(st.amount_raw[..8].try_into().unwrap()),
90_000_000_000_000_000,
"amount is the cell's balance, not a supply counter"
);
assert!(st.minter_raw.is_empty());
assert_eq!(st.is_minter(), None);
}
#[test]
fn a_padded_scheme_byte_is_narrowed_only_when_lossless() {
assert_eq!(scheme_byte(&[2, 0, 0, 0, 0, 0, 0, 0]), Some(2));
assert_eq!(scheme_byte(&[0]), Some(0));
assert_eq!(scheme_byte(&[2, 0, 0, 1]), None, "high byte set must refuse");
assert_eq!(scheme_byte(&[]), None);
}
#[test]
fn every_cell_family_names_a_real_template() {
let skeletons = crate::observed::observed_skeletons();
let names: Vec<&str> = skeletons.iter().map(|s| s.name).collect();
for fam in CELL_FAMILIES {
if fam.template == TOKEN_TEMPLATE {
continue; }
assert!(
names.contains(&fam.template),
"{} is registered as a cell family but no skeleton derives it",
fam.template
);
if let Some(mode) = fam.mode {
for s in skeletons.iter().filter(|s| s.name == fam.template) {
assert!(
s.params().contains(&mode),
"{} labels a mode slot {mode} that one of its skeletons does not derive",
fam.template
);
}
}
}
let kaspacom = CELL_FAMILIES
.iter()
.find(|f| f.template == "KaspaCom · token")
.expect("KaspaCom is a registered family");
assert_eq!(kaspacom.mode, Some("mint_mode"));
let generations: Vec<&crate::Skeleton> = skeletons
.iter()
.filter(|s| s.name == kaspacom.template)
.collect();
assert_eq!(generations.len(), 2, "the 8,076 B and the 2,671 B builds");
for s in generations {
assert_eq!(s.params().get(3), Some(&"mint_mode"));
}
for fam in CELL_FAMILIES.iter().filter(|f| f.template != kaspacom.template) {
assert_eq!(fam.mode, None, "{} labels no mode slot", fam.template);
}
}
#[test]
fn a_pinned_reveal_is_named_by_its_pin_family() {
let reg = Registry::default();
let zealous = include_bytes!("../fixtures/kcc0020/zealous_token_a.bin").as_slice();
assert_eq!(revealed_template(®, 0, zealous), Some("Zealous · token"));
for later in [
include_bytes!("../fixtures/kcc0020/zealous_token_2641_a.bin").as_slice(),
include_bytes!("../fixtures/kcc0020/zealous_token_3037_a.bin").as_slice(),
include_bytes!("../fixtures/kcc0020/zealous_token_3223_a.bin").as_slice(),
] {
assert_eq!(revealed_template(®, 0, later), Some("Zealous · token"), "each later generation reads under the same family");
assert!(locate_state_block(later).is_none());
}
let vector = include_bytes!("../fixtures/kcc0020/vector_template_a.bin").as_slice();
assert_eq!(
revealed_template(®, 0, vector),
Some(crate::kcc0020::SPEC_TOKEN_TEMPLATE)
);
assert!(crate::kcc0020::pinned_program_state(zealous).is_some());
assert!(crate::kcc0020::pinned_program_state(vector).is_some());
}
#[test]
fn cell_template_names_carry_every_spec_label_once() {
let names = cell_template_names();
assert!(names.contains(&crate::kcc0020::SPEC_TOKEN_TEMPLATE));
assert!(names.contains(&"Zealous · token"));
for pin in crate::kcc0020::pins() {
assert!(names.contains(&pin.family_label()), "{} is missing", pin.family_label());
}
let mut seen = std::collections::HashSet::new();
for name in &names {
assert!(seen.insert(*name), "{name} is listed twice");
assert!(is_cell_template(name), "{name} is listed but not recognised");
}
assert!(!is_cell_template("genesis0 · auction"));
assert!(!is_cell_template(MINTER_TEMPLATE));
}
}
#[cfg(test)]
mod family_splice_tests {
use super::*;
#[test]
fn splicing_a_cells_own_state_reproduces_it_exactly() {
let reg = Registry::default();
let program = include_bytes!("../fixtures/kcom_token_b.bin").as_slice();
let st = decode_token_state(®, 0, program).expect("decodes");
let mut amount = [0u8; 8];
amount.copy_from_slice(&st.amount_raw[..8]);
let spliced =
splice_family_state(®, program, &st.owner, st.identifier_type, &amount, None)
.expect("splices");
assert_eq!(spliced, program, "own state must round-trip byte for byte");
let mut spk = vec![0xaa, 0x20];
spk.extend_from_slice(&blake2b_256(program));
spk.push(0x87);
let (proven, rebuilt) = prove_output_state_any(
®, program, &spk, &st.owner, st.identifier_type, &amount, 0,
)
.expect("must prove against its own commitment");
assert_eq!(proven.owner, st.owner);
assert_eq!(proven.amount_raw, st.amount_raw);
assert_eq!(rebuilt, program, "the proven program IS the cell's program");
assert_eq!(proven.mints(), Some(false));
assert_eq!(proven.is_minter(), None, "the strict read stays strict");
}
#[test]
fn a_wrong_amount_cannot_pass_a_cells_commitment() {
let reg = Registry::default();
let program = include_bytes!("../fixtures/kcom_token_b.bin").as_slice();
let st = decode_token_state(®, 0, program).unwrap();
let mut spk = vec![0xaa, 0x20];
spk.extend_from_slice(&blake2b_256(program));
spk.push(0x87);
let lie = 999_999_999u64.to_le_bytes();
assert!(
prove_output_state_any(®, program, &spk, &st.owner, st.identifier_type, &lie, 0)
.is_none(),
"a fabricated amount must never prove against a real commitment"
);
let thief = [0x11u8; 32];
let mut amount = [0u8; 8];
amount.copy_from_slice(&st.amount_raw[..8]);
assert!(
prove_output_state_any(®, program, &spk, &thief, st.identifier_type, &amount, 0)
.is_none(),
"a fabricated owner must never prove against a real commitment"
);
}
#[test]
fn the_base_build_still_proves_through_the_original_splice() {
let reg = Registry::default();
let program = include_bytes!("../fixtures/kcc20_a_a.bin").as_slice();
if let Some(st) = decode_token_state(®, 0, program) {
let mut amount = [0u8; 8];
amount.copy_from_slice(&st.amount_raw[..8]);
let minter = *st.minter_raw.first().unwrap_or(&0);
let mut spk = vec![0xaa, 0x20];
spk.extend_from_slice(&blake2b_256(program));
spk.push(0x87);
assert!(
prove_output_state_any(
®, program, &spk, &st.owner, st.identifier_type, &amount, minter
)
.is_some(),
"the base build must still prove"
);
}
}
}
#[cfg(test)]
mod recovery_base_tests {
use super::*;
#[test]
fn a_launchpad_cell_is_usable_as_a_splice_base() {
let reg = Registry::default();
let program = include_bytes!("../fixtures/kcom_token_b.bin").as_slice();
assert!(!has_state_block(program), "not the base build's layout");
assert!(
is_spliceable_cell(®, program),
"but it must still be spliceable"
);
let base = canonical_cell_base(®, program).expect("must canonicalise");
assert_eq!(base.len(), program.len(), "canonicalising preserves length");
assert_ne!(base, program, "state must actually be zeroed");
let other = include_bytes!("../fixtures/kcom_token_a.bin").as_slice();
let other_base = canonical_cell_base(®, other).expect("must canonicalise");
assert_ne!(
other_base, base,
"different tokens must not share a canonical base"
);
assert_eq!(
canonical_cell_base(®, &base).expect("base recanonicalises"),
base,
"canonicalisation must be stable"
);
let st = decode_token_state(®, 0, program).unwrap();
let mut amount = [0u8; 8];
amount.copy_from_slice(&st.amount_raw[..8]);
let mut spk = vec![0xaa, 0x20];
spk.extend_from_slice(&blake2b_256(program));
spk.push(0x87);
assert!(
prove_output_state_any(®, &base, &spk, &st.owner, st.identifier_type, &amount, 0)
.is_some(),
"the canonical base must prove the cell it came from"
);
}
#[test]
fn a_non_cell_program_is_not_a_base() {
let reg = Registry::default();
let auction = include_bytes!("../fixtures/g0_auction_a.bin").as_slice();
assert!(!is_spliceable_cell(®, auction));
assert!(canonical_cell_base(®, auction).is_none());
}
}
#[cfg(test)]
mod build_aware_base_tests {
use super::*;
fn commitment(program: &[u8]) -> Vec<u8> {
let mut spk = vec![0xaa, 0x20];
spk.extend_from_slice(&blake2b_256(program));
spk.push(0x87);
spk
}
fn hex32(s: &str) -> [u8; 32] {
hex::decode(s).unwrap().try_into().unwrap()
}
#[test]
fn zealous_skeleton_lookup_is_build_aware() {
let reg = Registry::default();
let builds: [(&str, &[u8], Range<usize>); 3] = [
(
"zealous_token_1811_a",
include_bytes!("../fixtures/zealous_token_1811_a.bin"),
35..67,
),
(
"zealous_launch_b_a",
include_bytes!("../fixtures/zealous_launch_b_a.bin"),
2..34,
),
(
"zealous_launch_a_a",
include_bytes!("../fixtures/zealous_launch_a_a.bin"),
2..34,
),
];
for (name, program, owner_range) in builds {
assert_eq!(
reg.decode(0, program).template,
Some("Zealous · token cell"),
"{name}: one family name for every build"
);
let splicer = CellSplicer::new(®, program)
.unwrap_or_else(|| panic!("{name}: the skeleton of its own build must fit"));
assert_eq!(splicer.owner, owner_range, "{name}: owner slot");
assert!(!splicer.has_minter_slot(), "{name}: no minter flag in this family");
let bases = canonical_cell_bases(®, program);
assert_eq!(bases.len(), 1, "{name}: no mode slot, so exactly one base");
assert_eq!(bases[0].len(), program.len(), "{name}: length preserved");
assert_eq!(&bases[0][owner_range.clone()], &[0u8; 32], "{name}: owner zeroed");
let st = decode_token_state(®, 0, program).expect(name);
let mut amount = [0u8; 8];
amount.copy_from_slice(&st.amount_raw[..8]);
let from_base = CellSplicer::new(®, &bases[0]).expect(name);
assert_eq!(from_base.owner, owner_range, "{name}: same layout on the base");
let proven = from_base
.prove(&commitment(program), &st.owner, st.identifier_type, &amount, 0)
.unwrap_or_else(|| panic!("{name}: own state must prove"));
assert_eq!(proven.owner, st.owner);
assert_eq!(
from_base.program_for(&st.owner, st.identifier_type, &amount, 0).as_deref(),
Some(program),
"{name}: the proven program is the cell's own"
);
}
}
#[test]
fn kaspacom_mode_slot_yields_two_bases() {
let reg = Registry::default();
for (name, program) in [
("kcom_token_a", include_bytes!("../fixtures/kcom_token_a.bin").as_slice()),
("kcom_token_2671_a", include_bytes!("../fixtures/kcom_token_2671_a.bin")),
] {
assert_eq!(&program[53..61], &[0u8; 8], "{name}: mint_mode is 0");
assert_eq!(canonical_cell_bases(®, program).len(), 1, "{name}");
}
let parent = include_bytes!("../fixtures/recovery/kcom_2671_parent_56fc521e_0.bin");
for (name, program) in [
("kcom_token_b", include_bytes!("../fixtures/kcom_token_b.bin").as_slice()),
("kcom_token_2671_b", include_bytes!("../fixtures/kcom_token_2671_b.bin")),
("56fc521e:0", parent.as_slice()),
] {
assert_eq!(&program[53..61], &2u64.to_le_bytes(), "{name}: mint_mode is 2");
let bases = canonical_cell_bases(®, program);
assert_eq!(bases.len(), 2, "{name}: as-is plus mode-zeroed");
let differing: Vec<usize> = (0..program.len())
.filter(|&i| bases[0][i] != bases[1][i])
.collect();
assert!(
!differing.is_empty() && differing.iter().all(|i| (53..61).contains(i)),
"{name}: the variants differ only in the mode slot, got {differing:?}"
);
assert_eq!(&bases[0][53..61], &program[53..61], "{name}: first keeps the mode");
assert_eq!(&bases[1][53..61], &[0u8; 8], "{name}: second zeroes it");
for base in &bases {
assert_eq!(&base[2..34], &[0u8; 32], "{name}: owner zeroed in both");
assert_eq!(
canonical_cell_bases(®, base).first(),
Some(base),
"{name}: each variant is its own as-is base"
);
}
}
assert_eq!(
hex::encode(blake2b_256(parent)),
"7cec521a9350d30408dae91466aeedc31fa0f34b43d30d6d23c06f650f333820"
);
let bases = canonical_cell_bases(®, parent);
let kept = CellSplicer::new(®, &bases[0]).expect("mode-kept base prepares");
let zeroed = CellSplicer::new(®, &bases[1]).expect("mode-zeroed base prepares");
assert!(!kept.has_minter_slot() && !zeroed.has_minter_slot());
let owner = hex32("51fb56d74326e9f92d4866907994cb6442791f71f2a1a2fee0d408f1cdfa28d1");
let minted = 100_000_000u64.to_le_bytes();
let minted_program = zeroed.program_for(&owner, 0, &minted, 0).unwrap();
assert_eq!(
hex::encode(blake2b_256(&minted_program)),
"c0f06ce47fb7331dfff85508e5545b140fff8448c7ff0764c91f5fc549581e56"
);
let minted_spk = commitment(&minted_program);
assert!(zeroed.prove(&minted_spk, &owner, 0, &minted, 0).is_some());
assert!(
kept.prove(&minted_spk, &owner, 0, &minted, 0).is_none(),
"the mode-kept base cannot reach a paid-out cell"
);
let rest = 99_948_700_000_000u64.to_le_bytes();
let rest_program = kept.program_for(&owner, 0, &rest, 0).unwrap();
assert_eq!(
hex::encode(blake2b_256(&rest_program)),
"e41db11e44e0574111cdb44a3a8d063f09ce781809cc78cbfddee9d0e5084e6a"
);
let rest_spk = commitment(&rest_program);
assert!(kept.prove(&rest_spk, &owner, 0, &rest, 0).is_some());
assert!(
zeroed.prove(&rest_spk, &owner, 0, &rest, 0).is_none(),
"the mode-zeroed base cannot reach the remainder"
);
let st = kept.prove(&rest_spk, &owner, 0, &rest, 0).unwrap();
assert!(st.minter_raw.is_empty());
assert_eq!(st.amount_i64(), Some(99_948_700_000_000));
}
#[test]
fn has_minter_slot_follows_the_build() {
let reg = Registry::default();
let kcc20 = include_bytes!("../fixtures/kcc20_a_a.bin").as_slice();
assert!(CellSplicer::new(®, kcc20).unwrap().has_minter_slot());
for base in canonical_cell_bases(®, kcc20) {
assert!(CellSplicer::new(®, &base).unwrap().has_minter_slot());
}
let owner = [0x5au8; 32];
let amount = 7u64.to_le_bytes();
for (name, program) in [
("kcom_token_b", include_bytes!("../fixtures/kcom_token_b.bin").as_slice()),
("kcom_token_2671_b", include_bytes!("../fixtures/kcom_token_2671_b.bin")),
("zealous_launch_a_a", include_bytes!("../fixtures/zealous_launch_a_a.bin")),
("zealous_token_1811_a", include_bytes!("../fixtures/zealous_token_1811_a.bin")),
] {
let splicer = CellSplicer::new(®, program).expect(name);
assert!(!splicer.has_minter_slot(), "{name}");
assert_eq!(
splicer.program_for(&owner, 0, &amount, 0),
splicer.program_for(&owner, 0, &amount, 1),
"{name}: minter 0 and 1 splice identical bytes"
);
for base in canonical_cell_bases(®, program) {
assert!(!CellSplicer::new(®, &base).expect(name).has_minter_slot(), "{name}");
}
}
}
}
#[cfg(test)]
mod splice_cost_tests {
use super::*;
#[test]
#[cfg_attr(debug_assertions, ignore = "a timing check on optimized code: run it with --release")]
fn a_splice_attempt_is_cheap_enough_for_the_recovery_budget() {
let reg = Registry::default();
let program = include_bytes!("../fixtures/kcom_token_b.bin").as_slice();
let st = decode_token_state(®, 0, program).unwrap();
let mut amount = [0u8; 8];
amount.copy_from_slice(&st.amount_raw[..8]);
let splicer = CellSplicer::new(®, program).expect("prepares");
let mut spk = vec![0xaa, 0x20];
spk.extend_from_slice(&blake2b_256(program));
spk.push(0x87);
let n = 200;
let start = std::time::Instant::now();
for i in 0..n {
let mut owner = st.owner;
owner[0] = i as u8; std::hint::black_box(splicer.prove(&spk, &owner, st.identifier_type, &amount, 0));
}
let per = start.elapsed() / n;
assert!(
per < std::time::Duration::from_micros(200),
"one splice attempt took {per:?}; recovery's 3s budget would allow only \
~{} attempts, and the pass needs thousands",
3_000_000 / per.as_micros().max(1)
);
}
}