Skip to main content

whisky_common/data/
credentials.rs

1use serde_json::{json, Value};
2
3use crate::{data::{ByteString, Constr0, Constr1, PlutusDataJson}, WError};
4use whisky_macros::ImplConstr;
5
6use super::{byte_string, constr0, constr1};
7
8#[derive(Clone, Debug)]
9pub enum Credential {
10    VerificationKey(VerificationKey),
11    Script(Script),
12}
13
14impl Credential {
15    pub fn new((hash, is_script): (&str, bool)) -> Self {
16        if is_script {
17            Credential::Script(Script::from(hash))
18        } else {
19            Credential::VerificationKey(VerificationKey::from(hash))
20        }
21    }
22}
23
24#[derive(Clone, Debug, ImplConstr)]
25pub struct VerificationKey(pub Constr0<ByteString>);
26
27#[derive(Clone, Debug, ImplConstr)]
28pub struct Script(pub Constr1<ByteString>);
29
30impl PlutusDataJson for Credential {
31    fn to_json(&self) -> Value {
32        match self {
33            Credential::VerificationKey(vk) => vk.to_json(),
34            Credential::Script(script) => script.to_json(),
35        }
36    }
37
38    fn from_json(value: &Value) -> Result<Self, WError> {
39        let tag = value
40            .get("constructor")
41            .ok_or_else(|| WError::new("Credential::from_json", "missing 'constructor' field"))?
42            .as_u64()
43            .ok_or_else(|| WError::new("Credential::from_json", "invalid 'constructor' value"))?;
44
45        match tag {
46            0 => VerificationKey::from_json(value).map(Credential::VerificationKey),
47            1 => Script::from_json(value).map(Credential::Script),
48            _ => Err(WError::new(
49                "Credential::from_json",
50                &format!("unknown constructor tag: {}", tag),
51            )),
52        }
53    }
54}
55
56#[derive(Clone, Debug)]
57pub struct Address {
58    pub payment_key_hash: String,
59    pub stake_credential: Option<String>,
60    pub is_script_payment_key: bool,
61    pub is_script_stake_key: bool,
62}
63
64impl Address {
65    pub fn new(
66        payment_key_hash: &str,
67        stake_credential: Option<&str>,
68        is_script_payment_key: bool,
69        is_script_stake_key: bool,
70    ) -> Self {
71        Address {
72            payment_key_hash: payment_key_hash.to_string(),
73            stake_credential: stake_credential.map(|s| s.to_string()),
74            is_script_payment_key,
75            is_script_stake_key,
76        }
77    }
78}
79
80impl PlutusDataJson for Address {
81    fn to_json(&self) -> Value {
82        if self.is_script_payment_key {
83            script_address(
84                &self.payment_key_hash,
85                self.stake_credential.as_deref(),
86                self.is_script_stake_key,
87            )
88        } else {
89            pub_key_address(
90                &self.payment_key_hash,
91                self.stake_credential.as_deref(),
92                self.is_script_stake_key,
93            )
94        }
95    }
96
97    fn to_json_string(&self) -> String {
98        self.to_json().to_string()
99    }
100
101    fn to_constr_field(&self) -> Vec<serde_json::Value> {
102        vec![self.to_json()]
103    }
104
105    fn from_json(value: &Value) -> Result<Self, WError> {
106        // Address format: {"constructor": 0, "fields": [payment_credential, stake_credential_maybe]}
107        let tag = value
108            .get("constructor")
109            .ok_or_else(|| WError::new("Address::from_json", "missing 'constructor' field"))?
110            .as_u64()
111            .ok_or_else(|| WError::new("Address::from_json", "invalid 'constructor' value"))?;
112
113        if tag != 0 {
114            return Err(WError::new(
115                "Address::from_json",
116                &format!("expected constructor tag 0 for Address, got {}", tag),
117            ));
118        }
119
120        let fields = value
121            .get("fields")
122            .ok_or_else(|| WError::new("Address::from_json", "missing 'fields' field"))?
123            .as_array()
124            .ok_or_else(|| WError::new("Address::from_json", "invalid 'fields' value"))?;
125
126        if fields.len() != 2 {
127            return Err(WError::new(
128                "Address::from_json",
129                "expected 2 fields for Address",
130            ));
131        }
132
133        // Parse payment credential: {"constructor": 0/1, "fields": [{"bytes": "..."}]}
134        let payment_cred = &fields[0];
135        let payment_tag = payment_cred
136            .get("constructor")
137            .and_then(|c| c.as_u64())
138            .ok_or_else(|| WError::new("Address::from_json", "invalid payment credential"))?;
139        let is_script_payment_key = payment_tag == 1;
140
141        let payment_fields = payment_cred
142            .get("fields")
143            .and_then(|f| f.as_array())
144            .ok_or_else(|| WError::new("Address::from_json", "invalid payment credential fields"))?;
145
146        let payment_key_hash = payment_fields
147            .first()
148            .and_then(|f| f.get("bytes"))
149            .and_then(|b| b.as_str())
150            .ok_or_else(|| WError::new("Address::from_json", "invalid payment key hash"))?
151            .to_string();
152
153        // Parse stake credential: {"constructor": 0/1, "fields": [...]}
154        // Constr0 means Some(staking_hash), Constr1 means None
155        let stake_cred = &fields[1];
156        let stake_tag = stake_cred
157            .get("constructor")
158            .and_then(|c| c.as_u64())
159            .ok_or_else(|| WError::new("Address::from_json", "invalid stake credential"))?;
160
161        let (stake_credential, is_script_stake_key) = if stake_tag == 1 {
162            // None - no stake credential
163            (None, false)
164        } else {
165            // Some - has stake credential
166            let stake_fields = stake_cred
167                .get("fields")
168                .and_then(|f| f.as_array())
169                .ok_or_else(|| WError::new("Address::from_json", "invalid stake credential fields"))?;
170
171            if stake_fields.is_empty() {
172                (None, false)
173            } else {
174                // Navigate: Constr0([Constr0([Constr0/1([{"bytes": "..."}])])])
175                let inner_wrapper = stake_fields.first()
176                    .and_then(|f| f.get("fields"))
177                    .and_then(|f| f.as_array())
178                    .and_then(|f| f.first())
179                    .ok_or_else(|| WError::new("Address::from_json", "invalid stake credential structure"))?;
180
181                let inner_tag = inner_wrapper
182                    .get("constructor")
183                    .and_then(|c| c.as_u64())
184                    .ok_or_else(|| WError::new("Address::from_json", "invalid stake credential inner tag"))?;
185
186                let is_script = inner_tag == 1;
187
188                let stake_hash = inner_wrapper
189                    .get("fields")
190                    .and_then(|f| f.as_array())
191                    .and_then(|f| f.first())
192                    .and_then(|f| f.get("bytes"))
193                    .and_then(|b| b.as_str())
194                    .ok_or_else(|| WError::new("Address::from_json", "invalid stake key hash"))?
195                    .to_string();
196
197                (Some(stake_hash), is_script)
198            }
199        };
200
201        Ok(Address {
202            payment_key_hash,
203            stake_credential,
204            is_script_payment_key,
205            is_script_stake_key,
206        })
207    }
208}
209
210pub fn payment_pub_key_hash(pub_key_hash: &str) -> Value {
211    byte_string(pub_key_hash)
212}
213
214pub fn pub_key_hash(pub_key_hash: &str) -> Value {
215    byte_string(pub_key_hash)
216}
217
218pub fn maybe_staking_hash(stake_credential: &str, is_script_stake_key: bool) -> Value {
219    if stake_credential.is_empty() {
220        constr1(json!([]))
221    } else if is_script_stake_key {
222        constr0(vec![constr0(vec![constr1(vec![byte_string(
223            stake_credential,
224        )])])])
225    } else {
226        constr0(vec![constr0(vec![constr0(vec![byte_string(
227            stake_credential,
228        )])])])
229    }
230}
231
232pub fn pub_key_address(
233    bytes: &str,
234    stake_credential: Option<&str>,
235    is_script_stake_key: bool,
236) -> Value {
237    constr0(vec![
238        constr0(vec![byte_string(bytes)]),
239        maybe_staking_hash(stake_credential.unwrap_or(""), is_script_stake_key),
240    ])
241}
242
243pub fn script_address(
244    bytes: &str,
245    stake_credential: Option<&str>,
246    is_script_stake_key: bool,
247) -> Value {
248    constr0(vec![
249        constr1(vec![byte_string(bytes)]),
250        maybe_staking_hash(stake_credential.unwrap_or(""), is_script_stake_key),
251    ])
252}