|
| 1 | +# Clarity Serialization (`clarity-serialization`) |
| 2 | + |
| 3 | +[](https://www.gnu.org/licenses/gpl-3.0) |
| 4 | + |
| 5 | +A Rust crate for representing, serializing, and deserializing data types from the Stacks Clarity smart contract language. |
| 6 | + |
| 7 | +## Overview |
| 8 | + |
| 9 | +This crate provides the core components for working with Clarity data structures in Rust. It defines canonical Rust types for every Clarity value (e.g., `Value`, `TypeSignature`, `PrincipalData`) and implements the consensus-critical binary serialization and deserialization format used by the Stacks blockchain. |
| 10 | + |
| 11 | +## Key Features |
| 12 | + |
| 13 | +* **Canonical Data Structures**: Rust representations for all Clarity types, including `int`, `uint`, `bool`, `principal`, `optional`, `response`, `tuple`, `list`, `buffer`, and strings. |
| 14 | +* **Consensus-Compatible Binary Codec**: Implements the binary serialization and deserialization format required by the Stacks blockchain. |
| 15 | +* **Type Safety**: Includes type-checking logic (`admits`, `least_supertype`) for validating values against type signatures. |
| 16 | + |
| 17 | +## Quick Start: Usage Examples |
| 18 | + |
| 19 | +### Example 1: Serializing a Clarity Value to Hex |
| 20 | + |
| 21 | +This example demonstrates how to construct a complex Clarity `(tuple)` and serialize it to its hexadecimal string representation, which is suitable for use as a transaction argument. |
| 22 | + |
| 23 | +```rust |
| 24 | +use clarity_serialization::types::{Value, TupleData, PrincipalData}; |
| 25 | + |
| 26 | +fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 27 | + // 1. Construct the individual values that will go into our tuple. |
| 28 | + let id = Value::UInt(101); |
| 29 | + let owner = Value::Principal( |
| 30 | + PrincipalData::parse("SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G")? |
| 31 | + ); |
| 32 | + let metadata = Value::some( |
| 33 | + Value::buff_from(vec![0xde, 0xad, 0xbe, 0xef])? |
| 34 | + )?; |
| 35 | + |
| 36 | + // 2. Create a vec of name-value pairs for the tuple. |
| 37 | + let tuple_fields = vec![ |
| 38 | + ("id".into(), id), |
| 39 | + ("owner".into(), owner), |
| 40 | + ("metadata".into(), metadata), |
| 41 | + ]; |
| 42 | + |
| 43 | + // 3. Construct the tuple value. |
| 44 | + let my_tuple = Value::from(TupleData::from_data(tuple_fields)?); |
| 45 | + |
| 46 | + // 4. Serialize the tuple to its consensus-cricital hex string. |
| 47 | + let hex_string = my_tuple.serialize_to_hex()?; |
| 48 | + |
| 49 | + println!("Clarity Tuple: {}", my_tuple); |
| 50 | + println!("Serialized Hex: {}", hex_string); |
| 51 | + |
| 52 | + // The output `hex_string` can now be used in a contract-call transaction. |
| 53 | + assert_eq!( |
| 54 | + hex_string, |
| 55 | + "0c000000030269640100000000000000000000000000000065086d657461646174610a0200000004deadbeef056f776e65720514a46ff88886c2ef9762d970b4d2c63678835bd39d" |
| 56 | + ); |
| 57 | + |
| 58 | + Ok(()) |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +### Example 2: Deserializing a Clarity Value from Hex |
| 63 | + |
| 64 | +This example shows the reverse process: taking a hex string and deserializing it into a structured `Value` object, while validating it against an expected type. |
| 65 | + |
| 66 | +```rust |
| 67 | +use clarity_serialization::types::{Value, TypeSignature}; |
| 68 | + |
| 69 | +fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 70 | + let hex_string = "0c000000030269640100000000000000000000000000000065086d657461646174610a0200000004deadbeef056f776e65720514a46ff88886c2ef9762d970b4d2c63678835bd39d"; |
| 71 | + |
| 72 | + // 1. First, let's deserialize without a type for inspection. |
| 73 | + // NOTE: This is not recommended for production use with data from untrusted sources. |
| 74 | + let untyped_value = Value::try_deserialize_hex_untyped(hex_string)?; |
| 75 | + println!("Deserialized (untyped): {:?}", untyped_value); |
| 76 | + |
| 77 | + // 2. For robust deserialization, we should define the expected type. |
| 78 | + // This can be derived from the untyped value or known from a contract's interface. |
| 79 | + let expected_type = TypeSignature::type_of(&untyped_value)?; |
| 80 | + println!("Inferred Type Signature: {}", expected_type); |
| 81 | + |
| 82 | + // 3. Deserialize again, this time enforcing the type signature. |
| 83 | + // The `sanitize` flag should be `true` when reading values from the DB |
| 84 | + // that were stored before Stacks 2.4. For new values, it can be `false`. |
| 85 | + let typed_value = Value::try_deserialize_hex(hex_string, &expected_type, false)?; |
| 86 | + |
| 87 | + // 4. Now we can safely access the tuple's fields. |
| 88 | + let tuple_data = typed_value.expect_tuple()?; |
| 89 | + let id = tuple_data.get("id")?.clone().expect_u128()?; |
| 90 | + let owner = tuple_data.get("owner")?.clone().expect_principal()?; |
| 91 | + |
| 92 | + println!("Successfully deserialized and validated!"); |
| 93 | + println!("ID: {}", id); |
| 94 | + println!("Owner: {}", owner); |
| 95 | + |
| 96 | + Ok(()) |
| 97 | +} |
| 98 | +``` |
| 99 | + |
| 100 | +## Clarity Value Binary Format |
| 101 | + |
| 102 | +The crate implements the standard binary format for Clarity values. At a high level, every value is encoded as: `[Type Prefix Byte] + [Payload]`. |
| 103 | + |
| 104 | +| Type Prefix (Hex) | Clarity Type | Payload Description | |
| 105 | +| ----------------- | ----------------- | -------------------------------------------------------------------------------- | |
| 106 | +| `0x00` | `int` | 16-byte big-endian signed integer. | |
| 107 | +| `0x01` | `uint` | 16-byte big-endian unsigned integer. | |
| 108 | +| `0x02` | `(buff L)` | 4-byte big-endian length `L`, followed by `L` raw bytes. | |
| 109 | +| `0x03` | `true` | No payload. | |
| 110 | +| `0x04` | `false` | No payload. | |
| 111 | +| `0x05` | `principal` (Std) | 1-byte version, followed by 20-byte HASH160. | |
| 112 | +| `0x06` | `principal` (Cont)| Serialized Contract Principal (issuer) + 1-byte length-prefixed contract name. | |
| 113 | +| `0x07` | `(ok V)` | The serialized inner value `V`. | |
| 114 | +| `0x08` | `(err V)` | The serialized inner value `V`. | |
| 115 | +| `0x09` | `none` | No payload. | |
| 116 | +| `0x0a` | `(some V)` | The serialized inner value `V`. | |
| 117 | +| `0x0b` | `(list ...)` | 4-byte big-endian element count, followed by each serialized element. | |
| 118 | +| `0x0c` | `(tuple ...)` | 4-byte big-endian entry count, followed by each serialized `(name, value)` pair. | |
| 119 | +| `0x0d` | `(string-ascii L)`| 4-byte big-endian length `L`, followed by `L` ASCII bytes. | |
| 120 | +| `0x0e` | `(string-utf8 L)` | 4-byte big-endian byte-length `L`, followed by `L` UTF8 bytes. | |
| 121 | + |
| 122 | +## Crate Features |
| 123 | + |
| 124 | +This crate is designed to be minimal by default. Optional functionality is available via feature flags: |
| 125 | + |
| 126 | +* `developer-mode`: Enables additional debugging and logging information useful during development. |
| 127 | +* `testing`: Enables helper functions and data structures used exclusively for unit and integration testing. |
| 128 | +* `slog_json`: Integrates with `slog` for structured JSON logging. |
| 129 | +* `wasm-web` / `wasm-deterministic`: Enables builds for WebAssembly environments with different determinism guarantees. |
| 130 | + |
| 131 | +## Contributing |
| 132 | + |
| 133 | +Contributions are welcome! This crate is part of the `stacks-core` monorepo. Please see the [main repository's contributing guidelines](https://github.com/stacks-network/stacks-core/blob/master/CONTRIBUTING.md) for more details on the development process. |
| 134 | + |
| 135 | +## License |
| 136 | + |
| 137 | +This project is licensed under the **GNU General Public License v3.0** ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)). See the `LICENSE` file for details. |
0 commit comments