chia_sdk_driver/offers/
compress.rs

1use std::io::Read;
2
3use bech32::{u5, Variant};
4use chia_protocol::SpendBundle;
5use chia_puzzles::{
6    CAT_PUZZLE, NFT_METADATA_UPDATER_DEFAULT, NFT_OWNERSHIP_LAYER,
7    NFT_OWNERSHIP_TRANSFER_PROGRAM_ONE_WAY_CLAIM_WITH_ROYALTIES, NFT_STATE_LAYER,
8    P2_DELEGATED_PUZZLE_OR_HIDDEN_PUZZLE, SETTLEMENT_PAYMENT, SINGLETON_TOP_LAYER_V1_1,
9};
10use chia_traits::Streamable;
11use flate2::{
12    read::{ZlibDecoder, ZlibEncoder},
13    Compress, Compression, Decompress, FlushDecompress,
14};
15use hex_literal::hex;
16use once_cell::sync::Lazy;
17
18use crate::DriverError;
19
20pub fn compress_offer(spend_bundle: &SpendBundle) -> Result<Vec<u8>, DriverError> {
21    compress_offer_bytes(&spend_bundle.to_bytes()?)
22}
23
24pub fn decompress_offer(bytes: &[u8]) -> Result<SpendBundle, DriverError> {
25    Ok(SpendBundle::from_bytes(&decompress_offer_bytes(bytes)?)?)
26}
27
28pub fn encode_offer(spend_bundle: &SpendBundle) -> Result<String, DriverError> {
29    encode_offer_data(&compress_offer(spend_bundle)?)
30}
31
32pub fn decode_offer(text: &str) -> Result<SpendBundle, DriverError> {
33    decompress_offer(&decode_offer_data(text)?)
34}
35
36const CAT_PUZZLE_V1: [u8; 1420] = hex!(
37    "
38    ff02ffff01ff02ff5effff04ff02ffff04ffff04ff05ffff04ffff0bff2cff05
39    80ffff04ff0bff80808080ffff04ffff02ff17ff2f80ffff04ff5fffff04ffff
40    02ff2effff04ff02ffff04ff17ff80808080ffff04ffff0bff82027fff82057f
41    ff820b7f80ffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ff
42    ffff04ff820bffff80808080808080808080808080ffff04ffff01ffffffff81
43    ca3dff46ff0233ffff3c04ff01ff0181cbffffff02ff02ffff03ff05ffff01ff
44    02ff32ffff04ff02ffff04ff0dffff04ffff0bff22ffff0bff2cff3480ffff0b
45    ff22ffff0bff22ffff0bff2cff5c80ff0980ffff0bff22ff0bffff0bff2cff80
46    80808080ff8080808080ffff010b80ff0180ffff02ffff03ff0bffff01ff02ff
47    ff03ffff09ffff02ff2effff04ff02ffff04ff13ff80808080ff820b9f80ffff
48    01ff02ff26ffff04ff02ffff04ffff02ff13ffff04ff5fffff04ff17ffff04ff
49    2fffff04ff81bfffff04ff82017fffff04ff1bff8080808080808080ffff04ff
50    82017fff8080808080ffff01ff088080ff0180ffff01ff02ffff03ff17ffff01
51    ff02ffff03ffff20ff81bf80ffff0182017fffff01ff088080ff0180ffff01ff
52    088080ff018080ff0180ffff04ffff04ff05ff2780ffff04ffff10ff0bff5780
53    ff778080ff02ffff03ff05ffff01ff02ffff03ffff09ffff02ffff03ffff09ff
54    11ff7880ffff0159ff8080ff0180ffff01818f80ffff01ff02ff7affff04ff02
55    ffff04ff0dffff04ff0bffff04ffff04ff81b9ff82017980ff808080808080ff
56    ff01ff02ff5affff04ff02ffff04ffff02ffff03ffff09ff11ff7880ffff01ff
57    04ff78ffff04ffff02ff36ffff04ff02ffff04ff13ffff04ff29ffff04ffff0b
58    ff2cff5b80ffff04ff2bff80808080808080ff398080ffff01ff02ffff03ffff
59    09ff11ff2480ffff01ff04ff24ffff04ffff0bff20ff2980ff398080ffff0109
60    80ff018080ff0180ffff04ffff02ffff03ffff09ff11ff7880ffff0159ff8080
61    ff0180ffff04ffff02ff7affff04ff02ffff04ff0dffff04ff0bffff04ff17ff
62    808080808080ff80808080808080ff0180ffff01ff04ff80ffff04ff80ff1780
63    8080ff0180ffffff02ffff03ff05ffff01ff04ff09ffff02ff26ffff04ff02ff
64    ff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff0bff22ffff0bff
65    2cff5880ffff0bff22ffff0bff22ffff0bff2cff5c80ff0580ffff0bff22ffff
66    02ff32ffff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff
67    0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff
68    02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff
69    0dff8080808080ffff01ff0bff2cff058080ff0180ffff04ffff04ff28ffff04
70    ff5fff808080ffff02ff7effff04ff02ffff04ffff04ffff04ff2fff0580ffff
71    04ff5fff82017f8080ffff04ffff02ff7affff04ff02ffff04ff0bffff04ff05
72    ffff01ff808080808080ffff04ff17ffff04ff81bfffff04ff82017fffff04ff
73    ff0bff8204ffffff02ff36ffff04ff02ffff04ff09ffff04ff820affffff04ff
74    ff0bff2cff2d80ffff04ff15ff80808080808080ff8216ff80ffff04ff8205ff
75    ffff04ff820bffff808080808080808080808080ff02ff2affff04ff02ffff04
76    ff5fffff04ff3bffff04ffff02ffff03ff17ffff01ff09ff2dffff0bff27ffff
77    02ff36ffff04ff02ffff04ff29ffff04ff57ffff04ffff0bff2cff81b980ffff
78    04ff59ff80808080808080ff81b78080ff8080ff0180ffff04ff17ffff04ff05
79    ffff04ff8202ffffff04ffff04ffff04ff24ffff04ffff0bff7cff2fff82017f
80    80ff808080ffff04ffff04ff30ffff04ffff0bff81bfffff0bff7cff15ffff10
81    ff82017fffff11ff8202dfff2b80ff8202ff808080ff808080ff138080ff8080
82    8080808080808080ff018080
83    "
84);
85
86const SETTLEMENT_PAYMENT_V1: [u8; 267] = hex!(
87    "
88    ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff
89    333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff
90    02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff
91    04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff
92    0180ffff02ffff03ff05ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff
93    02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff02ffff03ff
94    ff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff8080
95    8080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101
96    ff058080ff0180ff018080
97    "
98);
99
100static COMPRESSION_ZDICT: Lazy<Vec<u8>> = Lazy::new(|| {
101    let mut bytes = Vec::new();
102    bytes.extend_from_slice(&P2_DELEGATED_PUZZLE_OR_HIDDEN_PUZZLE);
103    bytes.extend_from_slice(&CAT_PUZZLE_V1);
104    bytes.extend_from_slice(&SETTLEMENT_PAYMENT_V1);
105    bytes.extend_from_slice(&SINGLETON_TOP_LAYER_V1_1);
106    bytes.extend_from_slice(&NFT_STATE_LAYER);
107    bytes.extend_from_slice(&NFT_OWNERSHIP_LAYER);
108    bytes.extend_from_slice(&NFT_METADATA_UPDATER_DEFAULT);
109    bytes.extend_from_slice(&NFT_OWNERSHIP_TRANSFER_PROGRAM_ONE_WAY_CLAIM_WITH_ROYALTIES);
110    bytes.extend_from_slice(&CAT_PUZZLE);
111    bytes.extend_from_slice(&SETTLEMENT_PAYMENT);
112    bytes
113});
114
115fn compress_offer_bytes(bytes: &[u8]) -> Result<Vec<u8>, DriverError> {
116    let mut output = 6u16.to_be_bytes().to_vec();
117    output.extend(zlib_compress(bytes, &COMPRESSION_ZDICT)?);
118    Ok(output)
119}
120
121fn decompress_offer_bytes(bytes: &[u8]) -> Result<Vec<u8>, DriverError> {
122    let version_bytes: [u8; 2] = bytes
123        .get(0..2)
124        .ok_or(DriverError::MissingVersionPrefix)?
125        .try_into()?;
126
127    let version = u16::from_be_bytes(version_bytes);
128
129    if version > 6 {
130        return Err(DriverError::UnsupportedVersion);
131    }
132
133    zlib_decompress(&bytes[2..], &COMPRESSION_ZDICT)
134}
135
136fn zlib_compress(input: &[u8], zdict: &[u8]) -> std::io::Result<Vec<u8>> {
137    let mut compress = Compress::new(Compression::new(6), true);
138    compress.set_dictionary(zdict)?;
139    let mut encoder = ZlibEncoder::new_with_compress(input, compress);
140    let mut output = Vec::new();
141    encoder.read_to_end(&mut output)?;
142    Ok(output)
143}
144
145fn zlib_decompress(input: &[u8], zdict: &[u8]) -> Result<Vec<u8>, DriverError> {
146    let mut decompress = Decompress::new(true);
147
148    if decompress
149        .decompress(input, &mut [], FlushDecompress::Finish)
150        .is_ok()
151    {
152        return Err(DriverError::NotCompressed);
153    }
154
155    decompress.set_dictionary(zdict)?;
156    let i = decompress.total_in();
157    let mut decoder = ZlibDecoder::new_with_decompress(&input[usize::try_from(i)?..], decompress);
158    let mut output = Vec::new();
159    decoder.read_to_end(&mut output)?;
160    Ok(output)
161}
162
163fn encode_offer_data(offer: &[u8]) -> Result<String, DriverError> {
164    let data = bech32::convert_bits(offer, 8, 5, true)?
165        .into_iter()
166        .map(u5::try_from_u8)
167        .collect::<Result<Vec<_>, bech32::Error>>()?;
168    Ok(bech32::encode("offer", data, Variant::Bech32m)?)
169}
170
171fn decode_offer_data(offer: &str) -> Result<Vec<u8>, DriverError> {
172    let (hrp, data, variant) = bech32::decode(offer)?;
173
174    if variant != Variant::Bech32m {
175        return Err(DriverError::InvalidFormat);
176    }
177
178    if hrp.as_str() != "offer" {
179        return Err(DriverError::InvalidPrefix(hrp));
180    }
181
182    Ok(bech32::convert_bits(&data, 5, 8, false)?)
183}
184
185#[cfg(test)]
186mod tests {
187    use chia_protocol::SpendBundle;
188    use chia_traits::Streamable;
189
190    use super::*;
191
192    const COMPRESSED_OFFER: &str = include_str!("./test_data/compressed.offer");
193    const DECOMPRESSED_OFFER: &str = include_str!("./test_data/decompressed.offer");
194
195    #[test]
196    fn test_compression() {
197        let decompressed_offer = hex::decode(DECOMPRESSED_OFFER.trim()).unwrap();
198        let output = compress_offer_bytes(&decompressed_offer).unwrap();
199        assert_eq!(hex::encode(output), COMPRESSED_OFFER.trim());
200    }
201
202    #[test]
203    fn test_decompression() {
204        let compressed_offer = hex::decode(COMPRESSED_OFFER.trim()).unwrap();
205        let output = decompress_offer_bytes(&compressed_offer).unwrap();
206        assert_eq!(hex::encode(output), DECOMPRESSED_OFFER.trim());
207    }
208
209    #[test]
210    fn parse_spend_bundle() {
211        let decompressed_offer = hex::decode(DECOMPRESSED_OFFER.trim()).unwrap();
212        SpendBundle::from_bytes(&decompressed_offer).unwrap();
213    }
214
215    #[test]
216    fn test_encode_decode_offer_data() {
217        let offer = b"hello world";
218        let encoded = encode_offer_data(offer).unwrap();
219        let decoded = decode_offer_data(&encoded).unwrap();
220        assert_eq!(offer, decoded.as_slice());
221    }
222}