Skip to content

Commit

Permalink
feat: add ergo support
Browse files Browse the repository at this point in the history
  • Loading branch information
Alesfatalis committed Jan 19, 2025
1 parent 1177ced commit 419581b
Show file tree
Hide file tree
Showing 11 changed files with 718 additions and 0 deletions.
136 changes: 136 additions & 0 deletions libs/ur-registry-ffi/src/ergo/ergo_sign_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use serde::Deserialize;
use serde_json::json;
use uuid::Uuid;
use ur_registry::crypto_key_path::CryptoKeyPath;
use ur_registry::ergo::ergo_sign_request::{DataType, ErgoSignRequest};
use ur_registry::ergo::ergo_unspent_box::{ErgoAsset, ErgoUnspentBox};
use ur_registry::registry_types::ERGO_SIGN_REQUEST;
use crate::export;
use crate::util_internal::string_helper::remove_prefix_0x;

#[derive(Deserialize)]
struct Box {
box_id: String,
value: u64,
ergo_tree: String,
assets: Option<Vec<Asset>>
}

#[derive(Deserialize)]
struct Asset {
token_id: String,
amount: u64
}

export! {
@Java_com_keystone_sdk_KeystoneNativeSDK_generateErgoSignRequest
fn generate_ergo_sign_request(
request_id: &str,
sign_data: &str,
boxes: &str,
derivation_paths: &str,
origin: &str
) -> String {
let request_id = match Uuid::parse_str(request_id) {
Ok(id) => id,
Err(_) => return json!({"error" : "uuid is invalid"}).to_string(),
}.as_bytes().to_vec();

let boxes: Vec<ErgoUnspentBox> = match serde_json::from_str::<Vec<Box>>(boxes) {
Ok(v) => v,
Err(_) => return json!({"error": "box is invalid"}).to_string(),
}.iter().map(|ergo_box| {
let assets: Option<Vec<ErgoAsset>> = match &ergo_box.assets {
Some(a) => Some(
a.iter().map(|asset| {
ErgoAsset::new(
asset.token_id.clone(),
asset.amount,
)
}).collect()),
None => None,
};

ErgoUnspentBox::new(
ergo_box.box_id.clone(),
ergo_box.value,
ergo_box.ergo_tree.clone(),
assets
)
}).collect();

let derivation_paths: Vec<CryptoKeyPath> = match serde_json::from_str::<Vec<String>>(derivation_paths) {
Ok(paths) => paths,
Err(_) => return json!({"error" : "derivation paths are invalid"}).to_string(),
}.iter().map(|path| CryptoKeyPath::from_path(path.to_string(), None))
.flatten()
.collect();

if derivation_paths.len() == 0 {
return json!({"error" : "derivation paths is invalid"}).to_string();
}

let sign_date_bytes = match hex::decode(remove_prefix_0x(sign_data)) {
Ok(v) => v,
Err(_) => return json!({"error": "sign data is invalid"}).to_string(),
};

let origin = if origin.len() == 0 { None } else { Some(origin.to_string()) };

let cbor_bytes: Vec<u8> = match ErgoSignRequest::new(
request_id,
sign_date_bytes,
DataType::Transaction,
derivation_paths,
boxes,
origin,
).try_into() {
Ok(v) => v,
Err(_) => return json!({"error" : "sign data is invalid"}).to_string(),
};
let cbor_hex = hex::encode(cbor_bytes);
let ur = json!({
"type": ERGO_SIGN_REQUEST.get_type(),
"cbor": cbor_hex,
});
ur.to_string()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_generate_ergo_sign_request() {
let request_id = "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d";
let sign_data = "9402011a9f15bfac9379c882fe0b7ecb2288153ce4f2def4f272214fb80f8e2630f04c00000001fbbaac7337d051c10fc3da0ccb864f4d32d40027551e1c3ea3ce361f39b91e4003c0843d0008cd02dc5b9d9d2081889ef00e6452fb5ad1730df42444ceccb9ea02258256d2fbd262e4f25601006400c0843d1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304e4f2560000809bee020008cd0388fa54338147371023aacb846c96c57e72cdcd73bc85d20250467e5b79dfa2aae4f25601006400cd0388fa54338147371023aacb846c96c57e72cdcd73bc85d20250467e5b79dfa2aa0000";
let derivation_paths = r#"[
"m/44'/429'/0'/0/6"
]"#;

let unspent_boxes = r#"[
{
"box_id": "1a9f15bfac9379c882fe0b7ecb2288153ce4f2def4f272214fb80f8e2630f04c",
"value": 8000000,
"ergo_tree": "0008cd0388fa54338147371023aacb846c96c57e72cdcd73bc85d20250467e5b79dfa2aa",
"assets": [
{
"token_id": "fbbaac7337d051c10fc3da0ccb864f4d32d40027551e1c3ea3ce361f39b91e40",
"amount": 200
}
]
}
]"#;

let origin = "ergo-wallet";

let expect_result = "{\"cbor\":\"a601d825509b1deb4d3b7d4bad9bdd2b0d7b3dcb6d0259013a9402011a9f15bfac9379c882fe0b7ecb2288153ce4f2def4f272214fb80f8e2630f04c00000001fbbaac7337d051c10fc3da0ccb864f4d32d40027551e1c3ea3ce361f39b91e4003c0843d0008cd02dc5b9d9d2081889ef00e6452fb5ad1730df42444ceccb9ea02258256d2fbd262e4f25601006400c0843d1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304e4f2560000809bee020008cd0388fa54338147371023aacb846c96c57e72cdcd73bc85d20250467e5b79dfa2aae4f25601006400cd0388fa54338147371023aacb846c96c57e72cdcd73bc85d20250467e5b79dfa2aa000003010481d90130a1018a182cf51901adf500f500f406f40581d920d3a401784031613966313562666163393337396338383266653062376563623232383831353363653466326465663466323732323134666238306638653236333066303463021a007a12000378483030303863643033383866613534333338313437333731303233616163623834366339366335376537326364636437336263383564323032353034363765356237396466613261610481d920d4a2017840666262616163373333376430353163313066633364613063636238363466346433326434303032373535316531633365613363653336316633396239316534300218c8066b6572676f2d77616c6c6574\",\"type\":\"ergo-sign-request\"}";

assert_eq!(
expect_result,
generate_ergo_sign_request(request_id, sign_data, unspent_boxes, derivation_paths, origin)
);
}
}

73 changes: 73 additions & 0 deletions libs/ur-registry-ffi/src/ergo/ergo_signed_tx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use anyhow::{format_err, Error};
use serde_json::json;
use hex;
use uuid::Uuid;
use ur_registry::ergo::ergo_signed_tx::ErgoSignedTx;
use ur_registry::registry_types::ERGO_SIGNED_TX;
use crate::export;

export! {
@Java_com_keystone_sdk_KeystoneNativeSDK_parseErgoSignedTx
fn parse_ergo_signed_tx(ur_type: &str, cbor_hex: &str) -> String {
if ERGO_SIGNED_TX.get_type() != ur_type {
return json!({"error": "type not match"}).to_string();
}

let parse_signed_tx = || -> Result<(String, String), Error> {
let cbor = hex::decode(cbor_hex.to_string())?;
let ergo_signed_tx = ErgoSignedTx::try_from(cbor).map_err(|_| format_err!(""))?;
let uuid = ergo_signed_tx.get_request_id();
let uuid_hex = hex::encode(uuid);
let request_id = Uuid::parse_str(&uuid_hex)?.to_string();
let signed_transaction = hex::encode(ergo_signed_tx.get_signed_tx());
Ok((request_id, signed_transaction))
};
match parse_signed_tx() {
Ok((request_id, signed_transaction)) => json!({
"request_id": request_id,
"signed_tx": signed_transaction,
}).to_string(),
Err(_) => json!({"error": "signature is invalid"}).to_string(),
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parse_ergo_signed_tx() {
let signed_tx_cbor = "A201D825509B1DEB4D3B7D4BAD9BDD2B0D7B3DCB6D0259014C011A9F15BFAC9379C882FE0B7ECB2288153CE4F2DEF4F272214FB80F8E2630F04C38A3F024D30E683EE9BD4E2B65DE9EE3C4B29F051D65A7DC0D670E75A962326CF9DAA0D8BF32B067ED3E426B9BC29A3FF9C937F96E02FB9CE1000001FBBAAC7337D051C10FC3DA0CCB864F4D32D40027551E1C3EA3CE361F39B91E4003C0843D0008CD02DC5B9D9D2081889EF00E6452FB5AD1730DF42444CECCB9EA02258256D2FBD262E4F25601006400C0843D1005040004000E36100204A00B08CD0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798EA02D192A39A8CC7A701730073011001020402D19683030193A38CC7B2A57300000193C2B2A57301007473027303830108CDEEAC93B1A57304E4F2560000809BEE020008CD0388FA54338147371023AACB846C96C57E72CDCD73BC85D20250467E5B79DFA2AAE4F25601006400";
let expected_result = "{\"request_id\":\"9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d\",\"signed_tx\":\"011a9f15bfac9379c882fe0b7ecb2288153ce4f2def4f272214fb80f8e2630f04c38a3f024d30e683ee9bd4e2b65de9ee3c4b29f051d65a7dc0d670e75a962326cf9daa0d8bf32b067ed3e426b9bc29a3ff9c937f96e02fb9ce1000001fbbaac7337d051c10fc3da0ccb864f4d32d40027551e1c3ea3ce361f39b91e4003c0843d0008cd02dc5b9d9d2081889ef00e6452fb5ad1730df42444ceccb9ea02258256d2fbd262e4f25601006400c0843d1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304e4f2560000809bee020008cd0388fa54338147371023aacb846c96c57e72cdcd73bc85d20250467e5b79dfa2aae4f25601006400\"}";

assert_eq!(
expected_result,
parse_ergo_signed_tx("ergo-signed-tx", signed_tx_cbor)
);
}

#[test]
fn test_parse_ergo_signed_tx_type_error() {
let signed_tx_cbor = "A301D825509B1DEB4D3B7D4BAD9BDD2B0D7B3DCB6D025840B93921DB17F2F1D50BDA37B510F543151DF222E80946FEFBACFADFB2D4A79FDA4FACF0AE5B41D71EA3A7EBEA6AA88DE9577A788AEAB195B99B6A633C20E055030358207BAC671050FCBA0DD54F3930601C42AD36CC11BC0589ED8D3CEF3EFF1C49EF6E";
let expect_result = "{\"error\":\"type not match\"}";

assert_eq!(
expect_result,
parse_ergo_signed_tx("eth-signature", signed_tx_cbor)
);
}


#[test]
fn test_parse_ergo_signed_tx_error() {
let signed_tx_cbor = "a201";
let expect_result = "{\"error\":\"signature is invalid\"}";

assert_eq!(
expect_result,
parse_ergo_signed_tx("ergo-signature", signed_tx_cbor)
);
}
}

2 changes: 2 additions & 0 deletions libs/ur-registry-ffi/src/ergo/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod ergo_sign_request;
pub mod ergo_signed_tx;
1 change: 1 addition & 0 deletions libs/ur-registry-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ pub mod tron;
mod util_internal;
pub mod utils;
pub mod zcash;
pub mod ergo;

ffi_support::define_string_destructor!(keystone_sdk_destroy_string);
Loading

0 comments on commit 419581b

Please sign in to comment.