I get the public key is derived using IC’s threshold ECDSA system with BIP-32 derivation:
// 1. Get the canister's root ECDSA public key
pub async fn lazy_call_ecdsa_public_key() -> EcdsaPublicKey {
let key_id = EcdsaKeyId {
curve: EcdsaCurve::Secp256k1,
name: "dfx_test_key".to_string(), // or "test_key_1" for testnet
};
let (response,) = ecdsa_public_key(EcdsaPublicKeyArgument {
canister_id: None,
derivation_path: vec![],
key_id,
}).await.unwrap();
EcdsaPublicKey::from(response)
}
// 2. Derive a user-specific public key using BIP-32
fn derive_public_key(owner: &Principal, public_key: &EcdsaPublicKey) -> EcdsaPublicKey {
let derivation_path = DerivationPath::new(vec![
DerivationIndex(vec![1u8]), // Schema version
DerivationIndex(owner.as_slice().to_vec()), // User principal
]);
public_key.derive_new_public_key(&derivation_path).unwrap()
}
// 3. Generate SUI address from the derived public key
pub fn sui_address(&self) -> String {
let compressed_public_key = self.derived_public_key.as_ref().serialize_sec1(true); // 33 bytes
let mut address_input = vec![0x01]; // SUI signature scheme flag for ECDSA secp256k1
address_input.extend_from_slice(&compressed_public_key);
let hash = Blake2b256::digest(&address_input);
format!("0x{}", hex::encode(hash.as_slice()))
}
You can reproduce the error following this:
// Cargo.toml dependencies
[dependencies]
ic-cdk = "0.17"
candid = "0.10"
blake2 = "0.10"
base64 = "0.21"
serde_json = "1.0"
ic-crypto-ecdsa-secp256k1 = { git = "https://github.com/dfinity/ic", tag = "release-2024-06-26_23-01-base", package = "ic-crypto-ecdsa-secp256k1" }
// lib.rs - Minimal example
use ic_cdk::api::management_canister::ecdsa::*;
use blake2::{Blake2b, Digest};
use candid::Principal;
type Blake2b256 = Blake2b<blake2::digest::consts::U32>;
#[ic_cdk::update]
async fn test_sui_signature() -> String {
let caller = ic_cdk::caller();
// 1. Get derived public key (simplified)
let key_id = EcdsaKeyId {
curve: EcdsaCurve::Secp256k1,
name: "dfx_test_key".to_string(),
};
let derivation_path = vec![
vec![1u8], // schema
caller.as_slice().to_vec(), // user
];
// 2. Create a simple test message
let test_message = b"Hello SUI from IC!";
let message_hash: [u8; 32] = Blake2b256::digest(test_message).into();
// 3. Sign with ECDSA
let (signature_result,) = sign_with_ecdsa(SignWithEcdsaArgument {
message_hash: message_hash.to_vec(),
derivation_path,
key_id,
}).await.unwrap();
let signature: [u8; 64] = signature_result.signature.try_into().unwrap();
// 4. Get the public key for this derivation path
let (pubkey_result,) = ecdsa_public_key(EcdsaPublicKeyArgument {
canister_id: None,
derivation_path,
key_id,
}).await.unwrap();
// 5. Create SUI signature format
let mut sui_signature = Vec::new();
sui_signature.push(0x00); // SUI ECDSA scheme flag
sui_signature.extend_from_slice(&signature);
sui_signature.extend_from_slice(&pubkey_result.public_key);
format!(
"Message: {}\nHash: {}\nSignature: {}\nPublic Key: {}\nSUI Signature: {}",
hex::encode(test_message),
hex::encode(message_hash),
hex::encode(signature),
hex::encode(&pubkey_result.public_key),
base64::encode(&sui_signature)
)
}
Sample Invalid Signature Data
From my actual failing transaction:
Transaction Bytes (base64):
AAACAAgA4fUFAAAAAAAgUdoFiq9vJg3BBLWMxUmGDZ/7xSnBNBAneEefmCAehyQCAgABAQAAAQEDAAAAAAEBAK+b61C7IUbKCdgVVKjl3EH0t0MKr4tQ1YbuOTzrJfYKAaYKdjKQqe/xCQC3AKrIn3tv/2fGTynQLucZx80SVO4rBgAAAAAAAAAg7dGP6n7nD1cLDbxdrdXaZcEDS62CKY+e2KAH0bxGjKOvm+tQuyFGygnYFVSo5dxB9LdDCq+LUNWG7jk86yX2CugDAAAAAAAA6AMAAAAAAAAA
Intent Message (what I’m actually signing):
000000 + [decoded transaction bytes above]
Where 000000
= scope(0x00) + version(0x00) + app_id(0x00)
Message Hash (Blake2b-256 of intent message):
a7c4e8f2b3d1a6c9e5f8b2a4d7c3e9f1b8a5c2d6e3f7a1b9c4d8e2f5a8b1c6d9
(This is a sample - I can provide the actual hash from logs)
Raw ECDSA Signature (64 bytes):
46bd2de544b95b4495c0db997eec300f5188ee4ec9f7f3cdb7e86b2446b1a9297f0b6782448ba7dd4bc6e0d845fe25cb0c3b94e5a18a2b31a8e9e04ff4722c89
Compressed Public Key (33 bytes):
02e038c1c559c3ff138a184c3b3142c2ed195fed04d3ebfc3b905374e476d8db3d
Final SUI Signature (98 bytes, base64 encoded):
AEa9LeVEuVtElcDbmX7sMA9RiO5Oyffzzbfoa0RrGpKX8LZ4JEi6fdS8bg2EX+Jcsw85TloYorMajp4E/0ciyJAuA4wcdlnD/xOKGEwzsUQsLtGV/tBNPr/DuQU3TkdtPf=
Expected SUI Address:
0xafa5eb50bb2146ca09d81554a8e5dc41f4b7430aaf8b50d586ee393ceb25f60a
The signature verification passes in my canister using ic-crypto-ecdsa-secp256k1::PublicKey::verify_signature_prehashed()
, but SUI RPC rejects it with “Invalid signature”.