ICP canister fails to sign Sui blockchain transactions

I’ve been working on the ICP<>Sui integration the goal is to deliver and store data on Sui using an ICP canister written in Rust.

I’ve been developing a canister on the ICP that needs to sign and submit transactions to the Sui blockchain. While the canister successfully performs read operations (like getting balances, fetching objects, and dry-running transfers), I’m hitting a error with actual transaction submissions due to signature verification errors.

(
  variant {
    Err = "Transaction execution error: {\\"code\\":-32002,\\"message\\":\\"Invalid user signature: Required Signature from 0x9757c4d17ef230642cb3b723f6ba82882e219a1b9806f4fadc45eb2d54126b86 is absent [\\\\\\"0x5c085c98551acb429fb994b265e92cb2d0f4ee2f9f3728e376ee710dbc13cc5e\\\\\\"]\\"}"
  },
)

i’ve tried multiple approaches:

  • diff hashing algo (SHA, Blake2b, etc.)
  • diff signature schemes (ECDSA/secp256k1, Ed25519, Schnorr)
  • Creating Fresh Addresses

Despite all of this, the signed transaction continues to get rejected by Sui. Even they use the same signature type.

my full code

#[ic_cdk::update]
async fn store_data_on_sui(
    timestamp: u64,
    source: Vec<u8>,
    data: Vec<u8>,
) -> Result<SuiTransactionResult, String> {
    // config
    let (pkg, stat, key, path) = STATE.with(|s| {
        let st = s.borrow();
        (
            st.package_id.clone().ok_or("package unset".to_string()),
            st.stats_object_id.clone().ok_or("stats unset".to_string()),
            st.key_name.clone().ok_or("key unset".to_string()),
            st.derivation_path.clone().ok_or("path unset".to_string()),
        )
    });

    let pkg = pkg?;
    let stat = stat?;
    let key = key?;
    let path = path?;

    // Ed25519 public key from Schnorr API
    let pubkey_res = schnorr_public_key(&SchnorrPublicKeyArgs {
        canister_id: None,
        derivation_path: path.clone(),
        key_id: SchnorrKeyId {
            algorithm: SchnorrAlgorithm::Ed25519,
            name: key.clone(),
        },
    })
    .await
    .map_err(|e| format!("schnorr_public_key failed: {:?}", e))?;
    let pubkey = pubkey_res.public_key;

    // 32-byte Sui address (64 hex chars)
    let mut hasher = Sha3_256::new();
    hasher.update([0x00]); // Ed25519 scheme flag
    hasher.update(&pubkey);
    let hash = hasher.finalize();
    let sui_address = format!("0x{}", hex::encode(&hash));

    let move_args = json!([
        stat,                  // Shared stats object ID (32-byte hex string)
        timestamp.to_string(), // Plain u64 JSON number
        format!("0x{}", hex::encode(&source)),
        format!("0x{}", hex::encode(&data))
    ]);

    //unsafe_moveCall JSON-RPC request
    let build_tx = json!({
        "jsonrpc": "2.0",
        "id": 1,
        "method": "unsafe_moveCall",
        "params": [
            sui_address,
            pkg,
            "storage",
            "store_data",
            [],
            move_args,
            "0xd98c70cb706117642fcdb82ba56f93447451ac75ea541b2c36c1a6143edfd00b",
            "10000000",
            "Commit"
        ]
    });

    // Send request
    let resp = http_post(SUI_DEVNET, &build_tx).await?;
    let json_resp: Value =
        serde_json::from_str(&resp).map_err(|e| format!("Invalid JSON builder response: {}", e))?;
    ic_cdk::println!("Builder response: {}", resp);
    if let Some(err) = json_resp.get("error") {
        return Err(format!("Builder error: {}", err));
    }
    let tx_bytes_b64 = json_resp["result"]["txBytes"]
        .as_str()
        .ok_or("txBytes missing")?
        .to_string();
    ic_cdk::println!("TX BYTES_B64: {:?}", tx_bytes_b64);
    //Decode and form intent message
    let tx_bytes = base64::decode(&tx_bytes_b64).map_err(|e| format!("decode tx: {}", e))?;
    let intent = [0, 0, 0];

    let mut intent_msg = Vec::with_capacity(intent.len() + tx_bytes.len());
    intent_msg.extend_from_slice(&intent);
    intent_msg.extend_from_slice(&tx_bytes);

    use blake2b_simd::Params;
    let digest = Params::new().hash_length(32).hash(&intent_msg);

    let digest_bytes = digest.as_bytes();

    //sign intent message
    let sig_res = sign_with_schnorr(&SignWithSchnorrArgs {
        message: digest_bytes.to_vec(),
        derivation_path: path.clone(),
        key_id: SchnorrKeyId {
            algorithm: SchnorrAlgorithm::Ed25519,
            name: key.clone(),
        },
        aux: None,
    })
    .await
    .map_err(|e| format!("sign_with_schnorr failed: {:?}", e))?;
    let raw_sig = sig_res.signature;
    //Build signature blob
    let mut serialized_sig = Vec::new();
    serialized_sig.push(0x00);
    serialized_sig.extend_from_slice(&raw_sig);
    serialized_sig.extend_from_slice(&pubkey);
    let signature_b64 = base64::encode(&serialized_sig);
    //execute transaction
    let exec_tx = json!({
        "jsonrpc":"2.0","id":1,"method":"sui_executeTransactionBlock",
        "params":[
            tx_bytes_b64,
            [ signature_b64 ],
            {
            "showInput": true,
            "showRawInput": true,
            "showEffects": true,
            "showEvents": true,
            "showObjectChanges": true,
            "showBalanceChanges": true,
            "showRawEffects": false
            },
            "WaitForLocalExecution"
        ]
    });
    let exec_resp = http_post(SUI_DEVNET, &exec_tx).await?;
    let exec_json: Value = serde_json::from_str(&exec_resp)
        .map_err(|e| format!("Invalid JSON exec response: {}", e))?;
    if let Some(err) = exec_json.get("error") {
        return Err(format!("Execution error: {}", err));
    }
    let digest = exec_json["result"]["digest"]
        .as_str()
        .unwrap_or("")
        .to_string();
    let status = exec_json["result"]["effects"]["status"]["status"]
        .as_str()
        .unwrap_or("UNKNOWN")
        .to_string();
    let error_message = if status != "SUCCESS" {
        Some(exec_json["result"]["effects"]["status"]["error"].to_string())
    } else {
        None
    };

    Ok(SuiTransactionResult {
        digest,
        status,
        error_message,
    })
}

Are there known compatibility issues or specific requirements when using IC’s or sui cryptographic functions?

Any insights, suggestions, or solutions would be greatly appreciated.
Thank you!

Not well versed with SUI but dont think it support schnorr… correct me if I am wrong… Try without schnorr.

Already tried using ECDSA secp256k1 but getting the same error as with the schnorr one.