1use cardano_message_signing::{self as ms, Label};
2use cml_chain::address::Address;
3use cml_chain::certs::Credential;
4use cml_chain::crypto::hash;
5use cml_chain::{address::BaseAddress, certs::StakeCredential};
6use cml_crypto::chain_crypto::hash::Blake2b224;
7use cml_crypto::PublicKey;
8use cml_crypto::RawBytesEncoding;
9use cml_crypto::{Ed25519KeyHash, Ed25519Signature};
10use ms::utils::{FromBytes, ToBytes};
11use reqwest::header;
12use serde::{Deserialize, Serialize};
13use std::str;
14use wasm_bindgen::prelude::wasm_bindgen;
15use wasm_bindgen::JsError;
16
17#[wasm_bindgen]
22#[derive(Serialize, Deserialize)]
23pub struct DataSignature {
24 signature: Vec<u8>, key: Vec<u8>, }
27
28#[wasm_bindgen]
29impl DataSignature {
30 pub fn new(cose_signature: &[u8], cose_key: &[u8]) -> Self {
31 Self {
32 signature: cose_signature.to_vec(),
33 key: cose_key.to_vec(),
34 }
35 }
36 pub fn signature(&self) -> Vec<u8> {
37 self.signature.clone()
38 }
39 pub fn key(&self) -> Vec<u8> {
40 self.key.clone()
41 }
42}
43
44#[wasm_bindgen]
45#[derive(Serialize, Deserialize)]
46pub struct VerifyResponse {
47 error: String,
48 is_valid: bool,
49}
50
51#[wasm_bindgen]
52impl VerifyResponse {
53 pub fn new(error: &str, valid: bool) -> Self {
54 Self {
55 error: error.to_string(),
56 is_valid: valid,
57 }
58 }
59 pub fn error(&self) -> String {
60 self.error.clone()
61 }
62 pub fn is_valid(&self) -> bool {
63 self.is_valid.clone()
64 }
65}
66
67#[wasm_bindgen]
76pub fn verify_data_signature(
77 dt: DataSignature,
78 expected_message: &str,
79 expected_address_bech32: &str,
80) -> VerifyResponse {
81 let sign_res = verify_data_signature_cip008(dt, expected_message, expected_address_bech32);
82 match sign_res {
83 Ok(val) => VerifyResponse::new("", val),
84 Err(er) => VerifyResponse::new(&er.as_str(), false),
85 }
86}
87
88pub 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
164pub 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 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 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#[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
246pub 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 is_message_hashed = check_is_hashed_message(&cose_sign1).unwrap();
273
274 if is_message_hashed {
275 let expected_message_hash = Blake2b224::new(&expected_message.as_bytes()).to_string();
276 let payload_array: [u8; 28] = payload_to_verify
277 .as_slice()
278 .try_into()
279 .expect("Payload length is not 28 bytes");
280 let payload_text = Blake2b224::from(payload_array).to_string();
281 if expected_message_hash != payload_text {
282 println!("CIP30 Debug: Hashed message mismatch. Expected hash: {}, Got hash: {}", expected_message_hash, payload_text);
283 return Ok(false);
284 }
285 } else {
286 let payload_text = str::from_utf8(&payload_to_verify).unwrap_or_else(|_| "DEFAULT");
287 if expected_message != payload_text {
288 println!("CIP30 Debug: Unhashed message mismatch. Expected: '{}', Got: '{}'", expected_message, payload_text);
289 return Ok(false);
290 }
291
292 }
293
294 let cose_key_res = ms::COSEKey::from_bytes(dt.key);
295 if cose_key_res.is_err() {
296 return Err(cose_key_res.err().unwrap().to_string());
297 }
298
299 let cose_key = cose_key_res.unwrap();
300
301 let pk = PublicKey::from_raw_bytes(
302 &cose_key
303 .header(&ms::Label::new_int(&ms::utils::Int::new_negative(
304 ms::utils::BigNum::from_str("2").unwrap(),
305 )))
306 .unwrap()
307 .as_bytes()
308 .unwrap(),
309 )
310 .unwrap();
311
312 let is_address_valid_result = verify_signing_address_new(expected_address_bech32, &pk);
313
314 match is_address_valid_result {
315 Ok(true) => { }
316 Ok(false) => {
317 let pk_hash = pk.hash().to_string();
318 println!("CIP30 Debug: Address validation returned false. Expected Address: {}, Public Key Hash: {}", expected_address_bech32, pk_hash);
319 return Ok(false);
320 }
321 Err(e) => {
322 let pk_hash = pk.hash().to_string();
323 println!("CIP30 Debug: Address validation failed with error. Expected Address: {}, Public Key Hash: {}, Error: {:?}", expected_address_bech32, pk_hash, e);
326 return Ok(false);
329 }
330 }
331
332 let signed_data = cose_sign1.signed_data(None, None).unwrap().to_bytes();
333 let sig = Ed25519Signature::from_raw_bytes(&cose_sign1.signature()).unwrap();
334
335 if pk.verify(&signed_data, &sig) {
336 Ok(true)
337 } else {
338 println!("CIP30 Debug: Final signature verification failed.");
339 Ok(false)
340 }
341}
342
343fn check_is_hashed_message(
344 cose_sign1: &cardano_message_signing::COSESign1,
345) -> Result<bool, String> {
346 let headers_to_verify = cose_sign1.headers();
347 let unprotected_headers = headers_to_verify.unprotected();
348 let is_hashed_opt = unprotected_headers.header(&Label::new_text("hashed".to_string()));
349 if is_hashed_opt.is_none() {
350 return Err("No hashed header".to_string());
351 }
352 let is_hashed_cbor = is_hashed_opt.unwrap();
353 let is_hashed_bool = if let Some(b) = is_hashed_cbor.as_special().and_then(|s| s.as_bool()) {
354 b
355 } else if let Some(s) = is_hashed_cbor.as_text() {
356 s == "true"
357 } else {
358 return Err("Invalid type for hashed header".to_string());
359 };
360 if is_hashed_bool {
361 return Ok(true);
362 }
363 return Ok(false);
364}
365
366fn extract_pubkey(credential: &Credential) -> Option<&Ed25519KeyHash> {
367 match credential {
368 Credential::PubKey { hash, .. } => Some(hash),
369 _ => None,
370 }
371}
372
373pub fn verify_signing_address_new(
379 expected_address_bech32: &str,
380 cose_pubkey: &PublicKey,
381) -> Result<bool, JsError> {
382 let expected_credential = Address::from_bech32(expected_address_bech32)?
383 .staking_cred()
384 .expect("Address doesn't have staking credential")
385 .clone();
386
387 let expected_pubkeyhash = extract_pubkey(&expected_credential).unwrap();
388 let stake_key_hash = cose_pubkey.hash();
389 if expected_pubkeyhash.eq(&stake_key_hash) {
390 return Ok(true);
391 }
392 return Ok(false);
393}