use crate::types::FieldElement;
pub use starknet_crypto::{pedersen_hash, Signature};
use starknet_crypto::{rfc6979_generate_k, sign, verify, SignError, VerifyError};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum EcdsaSignError {
#[error("message hash out of range")]
MessageHashOutOfRange,
}
#[derive(Debug, Error)]
pub enum EcdsaVerifyError {
#[error("message hash out of range")]
MessageHashOutOfRange,
#[error("signature r value out of range")]
SignatureROutOfRange,
#[error("signature s value out of range")]
SignatureSOutOfRange,
}
pub fn compute_hash_on_elements(data: &[FieldElement]) -> FieldElement {
let mut current_hash = FieldElement::ZERO;
for item in data.iter() {
current_hash = pedersen_hash(¤t_hash, &(*item));
}
let data_len = FieldElement::from(data.len());
pedersen_hash(¤t_hash, &data_len)
}
pub fn ecdsa_sign(
private_key: &FieldElement,
message_hash: &FieldElement,
) -> Result<Signature, EcdsaSignError> {
let mut seed = None;
loop {
let k = rfc6979_generate_k(message_hash, private_key, seed.as_ref());
match sign(private_key, message_hash, &k) {
Ok(sig) => {
return Ok(Signature { r: sig.r, s: sig.s });
}
Err(SignError::InvalidMessageHash) => {
return Err(EcdsaSignError::MessageHashOutOfRange)
}
Err(SignError::InvalidK) => {
seed = match seed {
Some(prev_seed) => Some(prev_seed + FieldElement::ONE),
None => Some(FieldElement::ONE),
};
}
};
}
}
pub fn ecdsa_verify(
public_key: &FieldElement,
message_hash: &FieldElement,
signature: &Signature,
) -> Result<bool, EcdsaVerifyError> {
match verify(public_key, message_hash, &signature.r, &signature.s) {
Ok(result) => Ok(result),
Err(VerifyError::InvalidMessageHash) => Err(EcdsaVerifyError::MessageHashOutOfRange),
Err(VerifyError::InvalidR) => Err(EcdsaVerifyError::SignatureROutOfRange),
Err(VerifyError::InvalidS) => Err(EcdsaVerifyError::SignatureSOutOfRange),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_compute_hash_on_elements() {
let hash = compute_hash_on_elements(&[
FieldElement::from_hex_be("0xaa").unwrap(),
FieldElement::from_hex_be("0xbb").unwrap(),
FieldElement::from_hex_be("0xcc").unwrap(),
FieldElement::from_hex_be("0xdd").unwrap(),
]);
let expected_hash = FieldElement::from_hex_be(
"025cde77210b1c223b2c6e69db6e9021aa1599177ab177474d5326cd2a62cb69",
)
.unwrap();
assert_eq!(expected_hash, hash);
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_compute_hash_on_elements_empty_data() {
let hash = compute_hash_on_elements(&[]);
let expected_hash = FieldElement::from_hex_be(
"049ee3eba8c1600700ee1b87eb599f16716b0b1022947733551fde4050ca6804",
)
.unwrap();
assert_eq!(expected_hash, hash);
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_ecdsa_sign() {
let signature = ecdsa_sign(
&FieldElement::from_hex_be(
"0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79",
)
.unwrap(),
&FieldElement::from_hex_be(
"06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76",
)
.unwrap(),
)
.unwrap();
let expected_r = FieldElement::from_hex_be(
"061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f",
)
.unwrap();
let expected_s = FieldElement::from_hex_be(
"04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a",
)
.unwrap();
assert_eq!(signature.r, expected_r);
assert_eq!(signature.s, expected_s);
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_ecdsa_sign_message_hash_out_of_range() {
match ecdsa_sign(
&FieldElement::from_hex_be(
"0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79",
)
.unwrap(),
&FieldElement::from_hex_be(
"0800000000000000000000000000000000000000000000000000000000000000",
)
.unwrap(),
) {
Err(EcdsaSignError::MessageHashOutOfRange) => {}
_ => panic!("Should throw error on out of range message hash"),
};
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_ecdsa_verify_valid_signature() {
let public_key = FieldElement::from_hex_be(
"02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159",
)
.unwrap();
let message_hash = FieldElement::from_hex_be(
"06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76",
)
.unwrap();
let r = FieldElement::from_hex_be(
"061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f",
)
.unwrap();
let s = FieldElement::from_hex_be(
"04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a",
)
.unwrap();
assert!(ecdsa_verify(&public_key, &message_hash, &Signature { r, s }).unwrap());
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_ecdsa_verify_invalid_signature() {
let public_key = FieldElement::from_hex_be(
"02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159",
)
.unwrap();
let message_hash = FieldElement::from_hex_be(
"06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76",
)
.unwrap();
let r = FieldElement::from_hex_be(
"061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f",
)
.unwrap();
let s = FieldElement::from_hex_be(
"04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9b",
)
.unwrap();
assert!(!ecdsa_verify(&public_key, &message_hash, &Signature { r, s }).unwrap());
}
}