ckb_type_id/
lib.rs

1#![no_std]
2
3extern crate alloc;
4
5use alloc::vec::Vec;
6use ckb_std::{
7    ckb_constants::Source,
8    debug,
9    error::SysError,
10    high_level::{load_cell_type_hash, load_input, load_script, load_script_hash},
11    syscalls::load_cell,
12};
13use molecule::prelude::Entity;
14
15pub enum Error {
16    Syscall(SysError),
17    Native(TypeIDError),
18}
19pub enum TypeIDError {
20    // There can only be at most one input and at most one output type ID cell
21    InvalidTypeIDCellNum = 4,
22    // Type id does not match args
23    TypeIDNotMatch,
24    // Length of type id is incorrect
25    ArgsLengthNotEnough,
26}
27
28impl From<SysError> for Error {
29    fn from(s: SysError) -> Self {
30        Error::Syscall(s)
31    }
32}
33
34fn has_type_id_cell(index: usize, source: Source) -> bool {
35    let mut buf = Vec::new();
36    match load_cell(&mut buf, 0, index, source) {
37        Ok(_) => true,
38        Err(e) => {
39            // just confirm cell presence, no data needed
40            if let SysError::LengthNotEnough(_) = e {
41                return true;
42            }
43            debug!("load cell err: {:?}", e);
44            false
45        }
46    }
47}
48
49fn locate_first_type_id_output_index() -> Result<usize, Error> {
50    let current_script_hash = load_script_hash()?;
51
52    let mut i = 0;
53    loop {
54        let type_hash = load_cell_type_hash(i, Source::Output)?;
55
56        if type_hash == Some(current_script_hash) {
57            break;
58        }
59        i += 1
60    }
61    Ok(i)
62}
63
64/// Given a 32-byte type id, this function validates if
65/// current transaction confronts to the type ID rules.
66pub fn validate_type_id(type_id: [u8; 32]) -> Result<(), Error> {
67    if has_type_id_cell(1, Source::GroupInput) || has_type_id_cell(1, Source::GroupOutput) {
68        debug!("There can only be at most one input and at most one output type ID cell!");
69        return Err(Error::Native(TypeIDError::InvalidTypeIDCellNum));
70    }
71
72    if !has_type_id_cell(0, Source::GroupInput) {
73        // We are creating a new type ID cell here. Additional checkings are needed to ensure the type ID is legit.
74        let index = locate_first_type_id_output_index()?;
75
76        // The type ID is calculated as the blake2b (with CKB's personalization) of
77        // the first CellInput in current transaction, and the created output cell
78        // index(in 64-bit little endian unsigned integer).
79        let input = load_input(0, Source::Input)?;
80        let mut blake2b = blake2b_rs::Blake2bBuilder::new(32)
81            .personal(b"ckb-default-hash")
82            .build();
83        blake2b.update(input.as_slice());
84        blake2b.update(&index.to_le_bytes());
85        let mut ret = [0; 32];
86        blake2b.finalize(&mut ret);
87
88        if ret != type_id {
89            debug!("Invalid type ID!");
90            return Err(Error::Native(TypeIDError::TypeIDNotMatch));
91        }
92    }
93    Ok(())
94}
95
96/// Loading type ID from current script args, type_id must be at least 32 byte
97/// long.
98pub fn load_type_id_from_script_args(offset: usize) -> Result<[u8; 32], Error> {
99    let script = load_script()?;
100    let args = script.as_reader().args();
101    if offset + 32 > args.raw_data().len() {
102        debug!("Length of type id is incorrect!");
103        return Err(Error::Native(TypeIDError::ArgsLengthNotEnough));
104    }
105    let mut ret = [0; 32];
106    ret.copy_from_slice(&args.raw_data()[offset..offset + 32]);
107    Ok(ret)
108}