freenet_stdlib/contract_interface/
key.rs

1//! Contract key types and identifiers.
2//!
3//! This module provides the core types for identifying contracts:
4//! - `ContractInstanceId`: The hash of contract code and parameters (use for routing/lookup)
5//! - `ContractKey`: A complete key specification with code hash (use for storage/execution)
6
7use std::borrow::Borrow;
8use std::fmt::Display;
9use std::hash::{Hash, Hasher};
10use std::ops::Deref;
11use std::str::FromStr;
12
13use blake3::{traits::digest::Digest, Hasher as Blake3};
14use serde::{Deserialize, Serialize};
15use serde_with::serde_as;
16
17use crate::client_api::{TryFromFbs, WsApiError};
18use crate::code_hash::CodeHash;
19use crate::common_generated::common::ContractKey as FbsContractKey;
20use crate::parameters::Parameters;
21
22use super::code::ContractCode;
23use super::CONTRACT_KEY_SIZE;
24
25/// The key representing the hash of the contract executable code hash and a set of `parameters`.
26#[serde_as]
27#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)]
28#[cfg_attr(
29    any(feature = "testing", all(test, any(unix, windows))),
30    derive(arbitrary::Arbitrary)
31)]
32#[repr(transparent)]
33pub struct ContractInstanceId(#[serde_as(as = "[_; CONTRACT_KEY_SIZE]")] [u8; CONTRACT_KEY_SIZE]);
34
35impl ContractInstanceId {
36    pub fn from_params_and_code<'a>(
37        params: impl Borrow<Parameters<'a>>,
38        code: impl Borrow<ContractCode<'a>>,
39    ) -> Self {
40        generate_id(params.borrow(), code.borrow())
41    }
42
43    pub const fn new(key: [u8; CONTRACT_KEY_SIZE]) -> Self {
44        Self(key)
45    }
46
47    /// `Base58` string representation of the `contract id`.
48    pub fn encode(&self) -> String {
49        bs58::encode(self.0)
50            .with_alphabet(bs58::Alphabet::BITCOIN)
51            .into_string()
52    }
53
54    pub fn as_bytes(&self) -> &[u8] {
55        self.0.as_slice()
56    }
57
58    /// Build `ContractId` from the binary representation.
59    pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, bs58::decode::Error> {
60        let mut spec = [0; CONTRACT_KEY_SIZE];
61        bs58::decode(bytes)
62            .with_alphabet(bs58::Alphabet::BITCOIN)
63            .onto(&mut spec)?;
64        Ok(Self(spec))
65    }
66}
67
68impl Deref for ContractInstanceId {
69    type Target = [u8; CONTRACT_KEY_SIZE];
70
71    fn deref(&self) -> &Self::Target {
72        &self.0
73    }
74}
75
76impl FromStr for ContractInstanceId {
77    type Err = bs58::decode::Error;
78
79    fn from_str(s: &str) -> Result<Self, Self::Err> {
80        ContractInstanceId::from_bytes(s)
81    }
82}
83
84impl TryFrom<String> for ContractInstanceId {
85    type Error = bs58::decode::Error;
86
87    fn try_from(s: String) -> Result<Self, Self::Error> {
88        ContractInstanceId::from_bytes(s)
89    }
90}
91
92impl Display for ContractInstanceId {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        write!(f, "{}", self.encode())
95    }
96}
97
98impl std::fmt::Debug for ContractInstanceId {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        f.debug_tuple("ContractInstanceId")
101            .field(&self.encode())
102            .finish()
103    }
104}
105
106/// A complete key specification, that represents a cryptographic hash that identifies the contract.
107///
108/// This type always contains both the instance ID and the code hash.
109/// Use `ContractInstanceId` for operations that only need to identify the contract
110/// (routing, client requests), and `ContractKey` for operations that need the full
111/// specification (storage, execution).
112#[serde_as]
113#[derive(Debug, Eq, Copy, Clone, Serialize, Deserialize)]
114#[cfg_attr(
115    any(feature = "testing", all(test, any(unix, windows))),
116    derive(arbitrary::Arbitrary)
117)]
118pub struct ContractKey {
119    instance: ContractInstanceId,
120    code: CodeHash,
121}
122
123impl ContractKey {
124    pub fn from_params_and_code<'a>(
125        params: impl Borrow<Parameters<'a>>,
126        wasm_code: impl Borrow<ContractCode<'a>>,
127    ) -> Self {
128        let code = wasm_code.borrow();
129        let id = generate_id(params.borrow(), code);
130        let code_hash = *code.hash();
131        Self {
132            instance: id,
133            code: code_hash,
134        }
135    }
136
137    /// Gets the whole spec key hash.
138    pub fn as_bytes(&self) -> &[u8] {
139        self.instance.0.as_ref()
140    }
141
142    /// Returns the hash of the contract code.
143    pub fn code_hash(&self) -> &CodeHash {
144        &self.code
145    }
146
147    /// Returns the encoded hash of the contract code.
148    pub fn encoded_code_hash(&self) -> String {
149        bs58::encode(self.code.0)
150            .with_alphabet(bs58::Alphabet::BITCOIN)
151            .into_string()
152    }
153
154    /// Returns the contract key from the encoded hash of the contract code and the given
155    /// parameters.
156    pub fn from_params(
157        code_hash: impl Into<String>,
158        parameters: Parameters,
159    ) -> Result<Self, bs58::decode::Error> {
160        let mut code_key = [0; CONTRACT_KEY_SIZE];
161        bs58::decode(code_hash.into())
162            .with_alphabet(bs58::Alphabet::BITCOIN)
163            .onto(&mut code_key)?;
164
165        let mut hasher = Blake3::new();
166        hasher.update(code_key.as_slice());
167        hasher.update(parameters.as_ref());
168        let full_key_arr = hasher.finalize();
169
170        let mut spec = [0; CONTRACT_KEY_SIZE];
171        spec.copy_from_slice(&full_key_arr);
172        Ok(Self {
173            instance: ContractInstanceId(spec),
174            code: CodeHash(code_key),
175        })
176    }
177
178    /// Returns the `Base58` encoded string of the [`ContractInstanceId`](ContractInstanceId).
179    pub fn encoded_contract_id(&self) -> String {
180        self.instance.encode()
181    }
182
183    pub fn id(&self) -> &ContractInstanceId {
184        &self.instance
185    }
186
187    /// Constructs a ContractKey from a pre-computed instance ID and code hash.
188    ///
189    /// This is useful when the node needs to reconstruct a key from stored index data.
190    /// Callers must ensure the instance_id was correctly derived from the code_hash
191    /// and parameters, as this constructor does not verify consistency.
192    pub fn from_id_and_code(instance_id: ContractInstanceId, code_hash: CodeHash) -> Self {
193        Self {
194            instance: instance_id,
195            code: code_hash,
196        }
197    }
198}
199
200impl PartialEq for ContractKey {
201    fn eq(&self, other: &Self) -> bool {
202        self.instance == other.instance
203    }
204}
205
206impl std::hash::Hash for ContractKey {
207    fn hash<H: Hasher>(&self, state: &mut H) {
208        self.instance.0.hash(state);
209    }
210}
211
212impl From<ContractKey> for ContractInstanceId {
213    fn from(key: ContractKey) -> Self {
214        key.instance
215    }
216}
217
218impl Deref for ContractKey {
219    type Target = [u8; CONTRACT_KEY_SIZE];
220
221    fn deref(&self) -> &Self::Target {
222        &self.instance.0
223    }
224}
225
226impl std::fmt::Display for ContractKey {
227    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228        self.instance.fmt(f)
229    }
230}
231
232impl<'a> TryFromFbs<&FbsContractKey<'a>> for ContractKey {
233    fn try_decode_fbs(key: &FbsContractKey<'a>) -> Result<Self, WsApiError> {
234        let key_bytes: [u8; CONTRACT_KEY_SIZE] = key.instance().data().bytes().try_into().unwrap();
235        let instance = ContractInstanceId::new(key_bytes);
236        let code = key
237            .code()
238            .map(|code_hash| CodeHash::from_code(code_hash.bytes()))
239            .ok_or_else(|| WsApiError::deserialization("ContractKey missing code hash".into()))?;
240        Ok(ContractKey { instance, code })
241    }
242}
243
244fn generate_id<'a>(
245    parameters: &Parameters<'a>,
246    code_data: &ContractCode<'a>,
247) -> ContractInstanceId {
248    let contract_hash = code_data.hash();
249
250    let mut hasher = Blake3::new();
251    hasher.update(contract_hash.0.as_slice());
252    hasher.update(parameters.as_ref());
253    let full_key_arr = hasher.finalize();
254
255    debug_assert_eq!(full_key_arr[..].len(), CONTRACT_KEY_SIZE);
256    let mut spec = [0; CONTRACT_KEY_SIZE];
257    spec.copy_from_slice(&full_key_arr);
258    ContractInstanceId(spec)
259}
260
261#[inline]
262pub(super) fn internal_fmt_key(
263    key: &[u8; CONTRACT_KEY_SIZE],
264    f: &mut std::fmt::Formatter<'_>,
265) -> std::fmt::Result {
266    let r = bs58::encode(key)
267        .with_alphabet(bs58::Alphabet::BITCOIN)
268        .into_string();
269    write!(f, "{}", &r[..8])
270}