ic_web3_rs/
ic.rs

1//! IC's threshold ECDSA related functions
2
3use crate::signing;
4use crate::types::{Address, Recovery};
5use candid::{CandidType, Principal};
6use libsecp256k1::{recover, Message, PublicKey, PublicKeyFormat, RecoveryId, Signature};
7use serde::Serialize;
8use std::str::FromStr;
9
10const ECDSA_SIGN_CYCLES: u64 = 3 * 10_000_000_000;
11// pub type Address = [u8; 20];
12
13// #[derive(CandidType, Serialize, Debug, Clone)]
14// pub enum EcdsaCurve {
15//     #[serde(rename = "secp256k1")]
16//     secp256k1,
17// }
18
19use ic_cdk::api::management_canister::ecdsa::*;
20
21#[derive(CandidType, Serialize, Debug, Clone)]
22pub struct KeyInfo {
23    pub derivation_path: Vec<Vec<u8>>,
24    pub key_name: String,
25    pub ecdsa_sign_cycles: Option<u64>,
26}
27
28/// get public key from ic,
29/// derivation_path: 4-byte big-endian encoding of an unsigned integer less than 2^31
30pub async fn get_public_key(
31    canister_id: Option<Principal>,
32    derivation_path: Vec<Vec<u8>>,
33    key_name: String,
34) -> Result<Vec<u8>, String> {
35    let key_id = EcdsaKeyId {
36        curve: EcdsaCurve::Secp256k1,
37        name: key_name,
38    };
39    let ic_canister_id = "aaaaa-aa";
40    let ic = Principal::from_str(&ic_canister_id).unwrap();
41
42    let request = EcdsaPublicKeyArgument {
43        canister_id: canister_id,
44        derivation_path: derivation_path,
45        key_id: key_id.clone(),
46    };
47    let (res,): (EcdsaPublicKeyResponse,) = ic_cdk::call(ic, "ecdsa_public_key", (request,))
48        .await
49        .map_err(|e| format!("Failed to call ecdsa_public_key {}", e.1))?;
50
51    Ok(res.public_key)
52}
53
54/// convert compressed public key to ethereum address
55pub fn pubkey_to_address(pubkey: &[u8]) -> Result<Address, String> {
56    let uncompressed_pubkey = match PublicKey::parse_slice(pubkey, Some(PublicKeyFormat::Compressed)) {
57        Ok(key) => key.serialize(),
58        Err(_) => {
59            return Err("uncompress public key failed: ".to_string());
60        }
61    };
62    let hash = signing::keccak256(&uncompressed_pubkey[1..65]);
63    let mut result = [0u8; 20];
64    result.copy_from_slice(&hash[12..]);
65    Ok(Address::from(result))
66}
67
68/// get canister's eth address
69pub async fn get_eth_addr(
70    canister_id: Option<Principal>,
71    derivation_path: Option<Vec<Vec<u8>>>,
72    name: String,
73) -> Result<Address, String> {
74    let path = if let Some(v) = derivation_path {
75        v
76    } else {
77        vec![ic_cdk::id().as_slice().to_vec()]
78    };
79    match get_public_key(canister_id, path, name).await {
80        Ok(pubkey) => {
81            return pubkey_to_address(&pubkey);
82        }
83        Err(e) => {
84            return Err(e);
85        }
86    };
87}
88
89/// use ic's threshold ecdsa to sign a message
90pub async fn ic_raw_sign(message: Vec<u8>, key_info: KeyInfo) -> Result<Vec<u8>, String> {
91    assert!(message.len() == 32);
92
93    let key_id = EcdsaKeyId {
94        curve: EcdsaCurve::Secp256k1,
95        name: key_info.key_name,
96    };
97    let ic = Principal::management_canister();
98
99    let request = SignWithEcdsaArgument {
100        message_hash: message.clone(),
101        derivation_path: key_info.derivation_path,
102        key_id,
103    };
104
105    let ecdsa_sign_cycles = key_info.ecdsa_sign_cycles.unwrap_or(ECDSA_SIGN_CYCLES);
106
107    let (res,): (SignWithEcdsaResponse,) =
108        ic_cdk::api::call::call_with_payment(ic, "sign_with_ecdsa", (request,), ecdsa_sign_cycles)
109            .await
110            .map_err(|e| format!("Failed to call sign_with_ecdsa {}", e.1))?;
111
112    Ok(res.signature)
113}
114
115// recover address from signature
116// rec_id < 4
117pub fn recover_address(msg: Vec<u8>, sig: Vec<u8>, rec_id: u8) -> String {
118    let message = Message::parse_slice(&msg).unwrap();
119    let signature = Signature::parse_overflowing_slice(&sig).unwrap();
120    let recovery_id = RecoveryId::parse(rec_id).unwrap();
121
122    match recover(&message, &signature, &recovery_id) {
123        Ok(pubkey) => {
124            let uncompressed_pubkey = pubkey.serialize();
125            // let hash = keccak256_hash(&uncompressed_pubkey[1..65]);
126            let hash = signing::keccak256(&uncompressed_pubkey[1..65]);
127            let mut result = [0u8; 20];
128            result.copy_from_slice(&hash[12..]);
129            hex::encode(result)
130        }
131        Err(_) => "".into(),
132    }
133}
134
135pub fn verify(addr: String, message: Vec<u8>, signature: Vec<u8>) -> bool {
136    let (sig, rec_id) = Recovery::from_raw_signature(message.clone(), signature)
137        .unwrap()
138        .as_signature()
139        .unwrap();
140    let rec_addr = recover_address(message, sig.to_vec(), rec_id as u8);
141    return rec_addr == addr;
142}