ic_web3/
ic.rs

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