summon_api_sdk/
lib.rs

1use cardano_message_signing as ms;
2use cml_chain::address::Address;
3use cml_chain::certs::Credential;
4use cml_chain::{address::BaseAddress, certs::StakeCredential};
5use cml_crypto::{Ed25519KeyHash, Ed25519Signature};
6use cml_crypto::PublicKey;
7use cml_crypto::RawBytesEncoding;
8use ms::utils::{FromBytes, ToBytes};
9use serde::{Deserialize, Serialize};
10use std::str;
11use wasm_bindgen::prelude::wasm_bindgen;
12use wasm_bindgen::JsError;
13
14/**
15 *   signature:  COSESign1 bytes
16 *   key: COSEKey hex bytes
17 */
18#[wasm_bindgen]
19#[derive(Serialize, Deserialize)]
20pub struct DataSignature {
21    signature: Vec<u8>, //COSESign1 bytes
22    key: Vec<u8>,       //COSEKey bytes
23}
24
25#[wasm_bindgen]
26impl DataSignature {
27    pub fn new(cose_signature: &[u8], cose_key: &[u8]) -> Self {
28        Self {
29            signature: cose_signature.to_vec(),
30            key: cose_key.to_vec(),
31        }
32    }
33    pub fn signature(&self) -> Vec<u8> {
34        self.signature.clone()
35    }
36    pub fn key(&self) -> Vec<u8> {
37        self.key.clone()
38    }
39}
40
41#[wasm_bindgen]
42#[derive(Serialize, Deserialize)]
43pub struct VerifyResponse {
44    error: String,
45    is_valid: bool,
46}
47
48#[wasm_bindgen]
49impl VerifyResponse {
50    pub fn new(error: &str, valid: bool) -> Self {
51        Self {
52            error: error.to_string(),
53            is_valid: valid,
54        }
55    }
56    pub fn error(&self) -> String {
57        self.error.clone()
58    }
59    pub fn is_valid(&self) -> bool {
60        self.is_valid.clone()
61    }
62}
63
64/**
65 *  **Verify data signed according to CIP-008**
66 *   returns ->
67 *   VerifyResponse {
68 *       error: String,
69 *       is_valid: bool,
70 *   }
71 */
72#[wasm_bindgen]
73pub fn verify_data_signature(
74    dt: DataSignature,
75    expected_message: &str,
76    expected_address_bech32: &str,
77) -> VerifyResponse {
78    let sign_res = verify_data_signature_cip008(dt, expected_message, expected_address_bech32);
79    match sign_res {
80        Ok(val) => VerifyResponse::new("", val),
81        Err(er) => VerifyResponse::new(
82            &er.as_str(),
83            false,
84        ),
85    }
86}
87
88/**
89 *  **Verify data signed according to CIP-008**
90 *   returns ->
91 *    bool or error message
92 */
93pub fn verify_data_signature_cip008(
94    dt: DataSignature,
95    expected_message: &str,
96    expected_address_bech32: &str,
97) -> Result<bool, String> {
98    let cose_sign1 = ms::COSESign1::from_bytes(dt.signature).unwrap();
99
100    let payload_to_verify_opt = cose_sign1.payload();
101
102    if payload_to_verify_opt.is_none() {
103        return Ok(false);
104    }
105
106    let payload_to_verify = payload_to_verify_opt.unwrap();
107
108    let payload_text = str::from_utf8(&payload_to_verify).unwrap_or_else(|_| "DEFAULT");
109
110    if expected_message != payload_text {
111        return Ok(false);
112    }
113
114    let headers_to_verify = cose_sign1.headers();
115
116    let header_addr_val = headers_to_verify
117        .protected()
118        .deserialized_headers()
119        .header(&ms::Label::new_text("address".to_string()))
120        .unwrap()
121        .to_bytes();
122
123    let cbor_val_address = ms::cbor::CBORValue::from_bytes(header_addr_val)
124        .unwrap()
125        .as_bytes()
126        .unwrap();
127
128    let address_res = Address::from_raw_bytes(&cbor_val_address);
129
130    if address_res.is_err() {
131        return Err("failed parsing address from header".to_string());
132    }
133
134    let cose_key = ms::COSEKey::from_bytes(dt.key).unwrap();
135
136    let pk = PublicKey::from_raw_bytes(
137        &cose_key
138            .header(&ms::Label::new_int(&ms::utils::Int::new_negative(
139                ms::utils::BigNum::from_str("2").unwrap(),
140            )))
141            .unwrap()
142            .as_bytes()
143            .unwrap(),
144    )
145    .unwrap();
146
147    let is_address_valid =
148        verify_signing_address(expected_address_bech32, address_res.unwrap(), &pk);
149
150    if is_address_valid.is_err() || !is_address_valid.unwrap() {
151        return Ok(false);
152    }
153
154    let signed_data = cose_sign1.signed_data(None, None).unwrap().to_bytes();
155    let sig = Ed25519Signature::from_raw_bytes(&cose_sign1.signature()).unwrap();
156
157    if pk.verify(&signed_data, &sig) {
158        Ok(true)
159    } else {
160        Ok(false)
161    }
162}
163
164/**
165 *  **Verify signing address, needs to be full base address**
166 *   returns ->
167 *    Result<bool or error message>
168 */
169
170pub fn verify_signing_address(
171    expected_address_bech32: &str,
172    coseheader_address: Address,
173    cose_pubkey: &PublicKey,
174) -> Result<bool, JsError> {
175    let expected_address = Address::from_bech32(expected_address_bech32)?;
176    if coseheader_address.to_bech32(None)? != expected_address.to_bech32(None)? {
177        return Ok(false);
178    }
179
180    // check if BaseAddress
181    let base_address = BaseAddress::from_address(&coseheader_address);
182
183    if base_address.is_none() {
184        return Err(JsError::new("Signing address is not base address."));
185    }
186
187    //reconstruct address
188    let payment_key_hash = cose_pubkey.hash();
189    let stake_key = base_address.unwrap().stake;
190
191    let reconstructed_address = BaseAddress::new(
192        expected_address.network_id()?,
193        Credential::new_pub_key(payment_key_hash),
194        StakeCredential::from(stake_key),
195    );
196    if expected_address.to_bech32(None)? == reconstructed_address.to_address().to_bech32(None)? {
197        return Ok(true);
198    }
199    return Ok(false);
200}
201
202#[derive(Serialize, Deserialize)]
203#[wasm_bindgen]
204pub struct CustomClaim {
205    user_id: String,
206    address: String,
207}
208
209#[wasm_bindgen]
210impl CustomClaim {
211    pub fn new(user_id: &str, address: &str) -> Self {
212        Self {
213            user_id: user_id.to_string(),
214            address: address.to_string(),
215        }
216    }
217    pub fn user_id(&self) -> String {
218        self.user_id.clone()
219    }
220    pub fn address(&self) -> String {
221        self.address.clone()
222    }
223}
224
225/**
226 *  **Verify data signed according to CIP-30**
227 *   returns ->
228 *   VerifyResponse {
229 *       error: String,
230 *       is_valid: bool,
231 *   }
232 */
233#[wasm_bindgen]
234pub fn verify_data_signature_new(
235    dt: DataSignature,
236    expected_message: &str,
237    expected_address_bech32: &str,
238) -> VerifyResponse {
239    let sign_res = verify_data_signature_cip30(dt, expected_message, expected_address_bech32);
240    match sign_res {
241        Ok(val) => VerifyResponse::new("", val),
242        Err(er) => VerifyResponse::new(&er.as_str(), false),
243    }
244}
245
246/**
247 *  **Verify data signed according to CIP-30**
248 *   returns ->
249 *    bool or error message
250 */
251pub fn verify_data_signature_cip30(
252    dt: DataSignature,
253    expected_message: &str,
254    expected_address_bech32: &str,
255) -> Result<bool, String> {
256    let cose_sign1_res = ms::COSESign1::from_bytes(dt.signature);
257
258    if cose_sign1_res.is_err() {
259        return Err("Cose sign object could not be created from the signature".to_string());
260    }
261
262    let cose_sign1 = cose_sign1_res.unwrap();
263
264    let payload_to_verify_opt = cose_sign1.payload();
265
266    if payload_to_verify_opt.is_none() {
267        return Err("No payload".to_string());
268    }
269
270    let payload_to_verify = payload_to_verify_opt.unwrap();
271
272    let payload_text = str::from_utf8(&payload_to_verify).unwrap_or_else(|_| "DEFAULT");
273
274    if expected_message != payload_text {
275        return Ok(false);
276    }
277
278    let cose_key_res = ms::COSEKey::from_bytes(dt.key);
279    if cose_key_res.is_err() {
280        return Err(cose_key_res.err().unwrap().to_string());
281    }
282
283    let cose_key = cose_key_res.unwrap();
284
285    let pk = PublicKey::from_raw_bytes(
286        &cose_key
287            .header(&ms::Label::new_int(&ms::utils::Int::new_negative(
288                ms::utils::BigNum::from_str("2").unwrap(),
289            )))
290            .unwrap()
291            .as_bytes()
292            .unwrap(),
293    )
294    .unwrap();
295
296    let is_address_valid = verify_signing_address_new(expected_address_bech32, &pk);
297
298    if is_address_valid.is_err() || !is_address_valid.unwrap() {
299        return Ok(false);
300    }
301
302    let signed_data = cose_sign1.signed_data(None, None).unwrap().to_bytes();
303    let sig = Ed25519Signature::from_raw_bytes(&cose_sign1.signature()).unwrap();
304
305    if pk.verify(&signed_data, &sig) {
306        Ok(true)
307    } else {
308        Ok(false)
309    }
310}
311
312fn extract_pubkey(credential: &Credential) -> Option<&Ed25519KeyHash> {
313    match credential {
314        Credential::PubKey { hash, .. } => Some(hash),
315        _ => None,
316    }
317}
318
319/**
320 *  **Verify signing address, needs to be full base address**
321 *   returns ->
322 *    Result ( bool or error message )
323 */
324pub fn verify_signing_address_new(
325    expected_address_bech32: &str,
326    cose_pubkey: &PublicKey,
327) -> Result<bool, JsError> {
328    let expected_credential = Address::from_bech32(expected_address_bech32)?
329        .staking_cred()
330        .expect("Address doesn't have staking credential").clone();
331
332    let expected_pubkeyhash = extract_pubkey(&expected_credential).unwrap();
333    let stake_key_hash = cose_pubkey.hash();
334    if expected_pubkeyhash.eq(&stake_key_hash) {
335        return Ok(true); 
336    }
337    return Ok(false);
338}