Skip to content

Commit

Permalink
feat: add sui sign hash request
Browse files Browse the repository at this point in the history
  • Loading branch information
ZhenQian committed Nov 7, 2024
1 parent 76d0623 commit 60c8c07
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 6 deletions.
2 changes: 1 addition & 1 deletion libs/ur-parse-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ edition = "2021"

[dependencies]
ur-registry = { path = "../ur-registry" }
ur = { git = "https://github.com/KeystoneHQ/ur-rs", tag = "0.3.1", default-features = false}
ur = { git = "https://github.com/KeystoneHQ/ur-rs", tag = "0.3.1", default-features = false }
hex = { version = "0.4.3", features = ["alloc"], default-features = false }
56 changes: 54 additions & 2 deletions libs/ur-parse-lib/src/keystone_ur_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,13 @@ impl<T: fmt::Debug> fmt::Debug for URParseResult<T> {
mod tests {
use crate::keystone_ur_decoder::{probe_decode, MultiURParseResult, URParseResult};
use alloc::string::ToString;
use alloc::vec;
use alloc::{string::String, vec::Vec};
use ur_registry::crypto_key_path::CryptoKeyPath;
use ur_registry::crypto_key_path::PathComponent;
use ur_registry::crypto_psbt::CryptoPSBT;

use ur_registry::ethereum::eth_sign_request::EthSignRequest;

use ur_registry::sui::sui_sign_request::SuiSignRequest;
#[test]
fn test_decode_psbt() {
let ur = "ur:crypto-psbt/hdcxlkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypkvoonhknt";
Expand Down Expand Up @@ -166,4 +169,53 @@ mod tests {
hex::encode(crypto.get_sign_data()).to_lowercase());
}
}

#[test]
fn test_decode_sui_sign_request() {
let ur3 = "UR:SUI-SIGN-REQUEST/1268-2/LPCFAAWKAOCFADTTCYBENTKOSPHDWLATROUTDAGDSAIOSPYKDYRTDLBDSAGWCAENTPNNVWMOAYCEWETPIDYTTADWGHBZWFKBHFGROTHLOEAHRSDLFRADKNGHAAVLGOAHHDEMMWYLBZBKWLLKDKGESBHFAHNBLSWLOXAALUBWFLSRCTWELFVOKKLEURENZTCSTIPRNTRSOTGEZTHNGLDIWTMYDSQDFDMDWYPDIYTEMKMSFMFHETSALFCAKIGUCFCTGDFDLNSOSFZEGLSSCTJSGEPSZCSTEYNSYTFESGJPGTDETSFDWETOCESNUEWSYAQZYANELGZCOEAOAOADAEDPDYAEADAEADADAOAXLYTPAXDYWZGABNTTRTRPOLWKCYAMLBNBLNONMWESETIATOSEAXKNYNTELUDWLSESYTUOAENERYZEWTQDWKQZDMLUAETOGYGTIHGMHTFZBNWTSOPYMWZEPRHTQDKTSBCFGMURIDLTWTWPLPUEWFKN";
let ur1 = "UR:SUI-SIGN-REQUEST/418-2/LPCFADOEAOCFADTTCYBENTKOSPHDWLONADTPDAGDSAIOVSSSWFIAFGGLNEDKSKFTPYYAGWVTAOHKADJPAEAEAEAEAEAXADAEMNHHCEBKDIBDRLFRCPKNTDTTJOCWDISFRYWEYKADGMGSLEIDLTHKJSBTKSKOLTJNWDOERHAHAEAEAEAECXHPKELEURENZTETDPRKRKLYEEBZPYFTNDPRCFCAJYSEWNEEGMTNKGLONYGASODRIHAEAYUOAEAEAEAEAEAEAEAECXRYWNEOBZLFECYTKGMSOLGDSRFNHFSTASGYDACYFMSATKRFOYZSBBISCMNTLGZCOEAOAOADAEAEADADADAEADADAOAEAEADAOAEGDFDLNSOWPFXRSYLBKWFLBGOLNGDMWSFFTKKNSREFYKKWZGMTEBNTEJSLBBZWPUOAOASZSFZLUVYPDUYMWAOJEEEYTVDCKESZEOECYOSFLMYSNGWHEPYTOKEADPKBDVOLRWPGMBDRKJS";
let ur2 = "UR:SUI-SIGN-REQUEST/1072-2/LPCFAADYAOCFADTTCYBENTKOSPHDWLOERHAHAEAEAEAECXEHSROTINFEHLJETPBNJKIYPKJPBKFEWPPKIDYTTADWGHCMWZKBTPCHRSHGLPBAAYBBCFKGPDLPJYYAJPSOVWTNHSYNFLFGIAWYOTBWRDHPKITBAALRGLOLEYCMFLSRCTWEOERHAHAEAEAEAECXZCASDSFMMSHEHGHTTLMDWLMOGMJPRHOYRFJPCAHPAOUEYLBZHLSALESEKIGUCFCTGDFDLNSOWPFXRSYLBKWFLBGOLNGDMWSFFTKKNSREFYKKWZGMTEBNTEJSLBBZWPUOWYAOAEAEAEAEAEAEAEDPEHADAEAEAEAEAEAXLYTAADDYOEADLECSDWYKCFAXBEYKAEYKAEYKAEYKAOCYGMJYFLAXAALYHDCXGDFDLNSOWPFXRSYLBKWFLBGOLNGDMWSFFTKKNSREFYKKWZGMTEBNTEJSLBBZWPUOAHIHGUKPINIHJYAESTZSVEGA";
let result: URParseResult<SuiSignRequest> =
probe_decode(ur1.to_string().to_lowercase()).unwrap();
if result.is_multi_part {
let mut decoder = result.decoder.unwrap();
let _result: MultiURParseResult<SuiSignRequest> =
decoder.parse_ur(ur2.to_string().to_lowercase()).unwrap();
let result: MultiURParseResult<SuiSignRequest> =
decoder.parse_ur(ur3.to_string().to_lowercase()).unwrap();
let sui_sign_request = result.data.unwrap();
assert_eq!("00000000000301008e5c1c0a270bb73b227ad2d1701b27ccbdedf501524c8a628759710d7876876deaa2b90500000000205b7c8adf36fc382dbbbb813415ab3a9bb2191d74c1f13452da7b889a49c92a650008dc000000000000000020bdf133158235f97b97a650c33c56c70951251a3ec2cfbca1fa1468169d8dfda20202010000010101000101020000010200504886c9ec43bff70af37f55865094cc3a799cb54479f252d30cd3717f15ecdc0209fa408be1a8db94026b34f9e71e39fea21aa7478fcd4f5fabce7c01aa0be284eca2b905000000002031c3a369455d6bd80c7366aa720a45ecaa62f9d92c5416f27ed817bf57850e0814197ba88574f872c9e5da61f6474663eea313ba5b7dd604844ea6321647c31feda2b9050000000020fd09263e975f575ad595e9925272b9a1bc721d5b02def7155dc28ac17d53191f504886c9ec43bff70af37f55865094cc3a799cb54479f252d30cd3717f15ecdcee02000000000000002d31010000000000",
hex::encode(sui_sign_request.get_intent_message()).to_lowercase());
let components = vec![
PathComponent::new(Some(44), true).unwrap(),
PathComponent::new(Some(784), true).unwrap(),
PathComponent::new(Some(0), true).unwrap(),
PathComponent::new(Some(0), true).unwrap(),
PathComponent::new(Some(0), true).unwrap(),
];
let source_fingerprint = hex::decode("52744703").unwrap().try_into().unwrap();
let crypto_key_path = vec![CryptoKeyPath::new(
components,
Some(source_fingerprint),
None,
)];
assert_eq!(crypto_key_path, sui_sign_request.get_derivation_paths());
// request id
assert_eq!(
"c267e8c4f363464e9f24c53aabf84fe0",
hex::encode(sui_sign_request.get_request_id().unwrap())
);
// addresses
let addresses = sui_sign_request.get_addresses().unwrap();
assert_eq!(
vec!["504886c9ec43bff70af37f55865094cc3a799cb54479f252d30cd3717f15ecdc"],
addresses
.iter()
.map(|a| hex::encode(a))
.collect::<Vec<String>>()
);
// origin origin
assert_eq!("Suiet", sui_sign_request.get_origin().unwrap());
}
}
}
4 changes: 2 additions & 2 deletions libs/ur-registry/src/crypto_key_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const COMPONENTS: u8 = 1;
const SOURCE_FINGERPRINT: u8 = 2;
const DEPTH: u8 = 3;

#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct PathComponent {
index: Option<u32>,
wildcard: bool,
Expand Down Expand Up @@ -66,7 +66,7 @@ impl PathComponent {
}
}

#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CryptoKeyPath {
components: Vec<PathComponent>,
source_fingerprint: Option<Fingerprint>,
Expand Down
3 changes: 2 additions & 1 deletion libs/ur-registry/src/macros_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ use crate::solana::{sol_sign_request::SolSignRequest, sol_signature::SolSignatur
use crate::stellar::{
stellar_sign_request::StellarSignRequest, stellar_signature::StellarSignature,
};
use crate::sui::sui_sign_request::SuiSignRequest;
use crate::sui::sui_signature::SuiSignature;
use crate::sui::{sui_sign_hash_request::SuiSignHashRequest, sui_sign_request::SuiSignRequest};
use crate::ton::{ton_sign_request::TonSignRequest, ton_signature::TonSignature};
use crate::{impl_cbor_bytes, impl_ur_try_from_cbor_bytes, impl_ur_try_into_cbor_bytes};
use alloc::string::ToString;
Expand Down Expand Up @@ -86,6 +86,7 @@ impl_cbor_bytes!(
StellarSignRequest,
StellarSignature,
SuiSignRequest,
SuiSignHashRequest,
SuiSignature,
TonSignature,
TonSignRequest,
Expand Down
4 changes: 4 additions & 0 deletions libs/ur-registry/src/registry_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub enum URType {
CosmosSignRequest(String),
EvmSignRequest(String),
SuiSignRequest(String),
SuiSignHashRequest(String),
TonSignRequest(String),
QRHardwareCall(String),
Bytes(String),
Expand All @@ -44,6 +45,7 @@ impl URType {
"near-sign-request" => Ok(URType::NearSignRequest(type_str.to_string())),
"aptos-sign-request" => Ok(URType::AptosSignRequest(type_str.to_string())),
"sui-sign-request" => Ok(URType::SuiSignRequest(type_str.to_string())),
"sui-sign-hash-request" => Ok(URType::SuiSignHashRequest(type_str.to_string())),
"cardano-sign-request" => Ok(URType::CardanoSignRequest(type_str.to_string())),
"cardano-sign-data-request" => Ok(URType::CardanoSignDataRequest(type_str.to_string())),
"cardano-sign-cip8-data-request" => {
Expand Down Expand Up @@ -77,6 +79,7 @@ impl URType {
URType::CardanoSignCip8DataRequest(type_str) => type_str.to_string(),
URType::CardanoCatalystVotingRegistrationRequest(type_str) => type_str.to_string(),
URType::SuiSignRequest(type_str) => type_str.to_string(),
URType::SuiSignHashRequest(type_str) => type_str.to_string(),
URType::CosmosSignRequest(type_str) => type_str.to_string(),
URType::EvmSignRequest(type_str) => type_str.to_string(),
URType::QRHardwareCall(type_str) => type_str.to_string(),
Expand Down Expand Up @@ -164,6 +167,7 @@ pub const CARDANO_SIGN_CIP8_DATA_SIGNATURE: RegistryType =
// Sui
pub const SUI_SIGN_REQUEST: RegistryType = RegistryType("sui-sign-request", Some(7101));
pub const SUI_SIGNATURE: RegistryType = RegistryType("sui-signature", Some(7102));
pub const SUI_SIGN_HASH_REQUEST: RegistryType = RegistryType("sui-sign-hash-request", Some(7103));
// Ton
pub const TON_SIGN_REQUEST: RegistryType = RegistryType("ton-sign-request", Some(7201));
pub const TON_SIGNATURE: RegistryType = RegistryType("ton-signature", Some(7202));
Expand Down
1 change: 1 addition & 0 deletions libs/ur-registry/src/sui/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod sui_sign_hash_request;
pub mod sui_sign_request;
pub mod sui_signature;
210 changes: 210 additions & 0 deletions libs/ur-registry/src/sui/sui_sign_hash_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use minicbor::data::{Int, Tag};

use crate::cbor::{cbor_array, cbor_map};
use crate::crypto_key_path::CryptoKeyPath;
use crate::impl_template_struct;
use crate::registry_types::{RegistryType, SUI_SIGN_HASH_REQUEST, UUID};
use crate::traits::{MapSize, RegistryItem};
use crate::types::Bytes;

const REQUEST_ID: u8 = 1;
const MESSAGE_HASH: u8 = 2;
const DERIVATION_PATHS: u8 = 3;
const ADDRESSES: u8 = 4;
const ORIGIN: u8 = 5;

impl_template_struct!(SuiSignHashRequest {
request_id: Option<Bytes>,
message_hash: String,
derivation_paths: Vec<CryptoKeyPath>,
addresses: Option<Vec<Bytes>>,
origin: Option<String>
});

impl RegistryItem for SuiSignHashRequest {
fn get_registry_type() -> RegistryType<'static> {
SUI_SIGN_HASH_REQUEST
}
}

impl MapSize for SuiSignHashRequest {
fn map_size(&self) -> u64 {
let mut size = 2;
if self.request_id.is_some() {
size += 1;
}
if self.addresses.is_some() {
size += 1;
}
if self.origin.is_some() {
size += 1;
}
size
}
}

impl<C> minicbor::Encode<C> for SuiSignHashRequest {
fn encode<W: minicbor::encode::Write>(
&self,
e: &mut minicbor::Encoder<W>,
ctx: &mut C,
) -> Result<(), minicbor::encode::Error<W::Error>> {
e.map(self.map_size())?;
if let Some(request_id) = self.get_request_id() {
e.int(Int::from(REQUEST_ID))?
.tag(Tag::Unassigned(UUID.get_tag()))?
.bytes(&request_id)?;
}
e.int(Int::from(MESSAGE_HASH))?
.str(&self.get_message_hash())?;

let derivation_paths = self.get_derivation_paths();
if derivation_paths.is_empty() {
return Err(minicbor::encode::Error::message(
"derivation paths is invalid",
));
}
e.int(Int::from(DERIVATION_PATHS))?
.array(derivation_paths.len() as u64)?;
for path in derivation_paths {
e.tag(Tag::Unassigned(
CryptoKeyPath::get_registry_type().get_tag(),
))?;
CryptoKeyPath::encode(&path, e, ctx)?;
}

if let Some(addresses) = self.get_addresses() {
e.int(Int::from(ADDRESSES))?.array(addresses.len() as u64)?;
for addr in addresses {
e.bytes(&addr)?;
}
}

if let Some(origin) = self.get_origin() {
e.int(Int::from(ORIGIN))?.str(&origin)?;
}

Ok(())
}
}

impl<'b, C> minicbor::Decode<'b, C> for SuiSignHashRequest {
fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result<Self, minicbor::decode::Error> {
let mut result = SuiSignHashRequest::default();

cbor_map(d, &mut result, |key, obj, d| {
let key =
u8::try_from(key).map_err(|e| minicbor::decode::Error::message(e.to_string()))?;
match key {
REQUEST_ID => {
let tag = d.tag()?;
if !tag.eq(&Tag::Unassigned(UUID.get_tag())) {
return Err(minicbor::decode::Error::message("UUID tag is invalid"));
}
obj.request_id = Some(d.bytes()?.to_vec());
}
MESSAGE_HASH => {
obj.message_hash = d.str()?.to_string();
}
DERIVATION_PATHS => {
cbor_array(d, &mut obj.derivation_paths, |_key, obj, d| {
let tag = d.tag()?;
if !tag.eq(&Tag::Unassigned(
CryptoKeyPath::get_registry_type().get_tag(),
)) {
return Err(minicbor::decode::Error::message(
"CryptoKeyPath tag is invalid",
));
}
obj.push(CryptoKeyPath::decode(d, ctx)?);
Ok(())
})?;
}
ADDRESSES => {
if obj.addresses.is_none() {
obj.addresses = Some(Vec::new())
}
cbor_array(d, &mut obj.addresses, |_key, obj, d| {
match obj {
Some(v) => v.push(d.bytes()?.to_vec()),
None => {}
}
Ok(())
})?;
}
ORIGIN => {
obj.origin = Some(d.str()?.to_string());
}
_ => {}
}
Ok(())
})?;
Ok(result)
}
}

#[cfg(test)]
mod tests {
use alloc::vec;
use alloc::vec::Vec;

use crate::crypto_key_path::PathComponent;

use super::*;

#[test]
fn test_encode() {
let components = vec![
PathComponent::new(Some(44), true).unwrap(),
PathComponent::new(Some(784), true).unwrap(),
PathComponent::new(Some(0), true).unwrap(),
PathComponent::new(Some(0), true).unwrap(),
PathComponent::new(Some(0), true).unwrap(),
];
let source_fingerprint = hex::decode("78230804").unwrap().try_into().unwrap();
let crypto_key_path = CryptoKeyPath::new(components, Some(source_fingerprint), None);
let sig = SuiSignHashRequest {
request_id: Some(hex::decode("9b1deb4d3b7d4bad9bdd2b0d7b3dcb6d").unwrap()),
message_hash: "00000000000200201ff915a5e9e32fdbe0135535b6c69a00a9809aaf7f7c0275d3239ca79db20d6400081027000000000000020200010101000101020000010000ebe623e33b7307f1350f8934beb3fb16baef0fc1b3f1b92868eec3944093886901a2e3e42930675d9571a467eb5d4b22553c93ccb84e9097972e02c490b4e7a22ab73200000000000020176c4727433105da34209f04ac3f22e192a2573d7948cb2fabde7d13a7f4f149ebe623e33b7307f1350f8934beb3fb16baef0fc1b3f1b92868eec39440938869e803000000000000640000000000000000".to_string(),
derivation_paths: vec![crypto_key_path],
addresses: Some(vec![hex::decode("ebe623e33b7307f1350f8934beb3fb16baef0fc1b3f1b92868eec39440938869").unwrap()]),
origin: Some("Sui Wallet".to_string())
};
let result: Vec<u8> = sig.try_into().unwrap();
let expect_result = "a501d825509b1deb4d3b7d4bad9bdd2b0d7b3dcb6d027901b830303030303030303030303230303230316666393135613565396533326664626530313335353335623663363961303061393830396161663766376330323735643332333963613739646232306436343030303831303237303030303030303030303030303230323030303130313031303030313031303230303030303130303030656265363233653333623733303766313335306638393334626562336662313662616566306663316233663162393238363865656333393434303933383836393031613265336534323933303637356439353731613436376562356434623232353533633933636362383465393039373937326530326334393062346537613232616237333230303030303030303030303032303137366334373237343333313035646133343230396630346163336632326531393261323537336437393438636232666162646537643133613766346631343965626536323365333362373330376631333530663839333462656233666231366261656630666331623366316239323836386565633339343430393338383639653830333030303030303030303030303634303030303030303030303030303030300381d90130a2018a182cf5190310f500f500f500f5021a7823080404815820ebe623e33b7307f1350f8934beb3fb16baef0fc1b3f1b92868eec39440938869056a5375692057616c6c6574";

assert_eq!(expect_result, hex::encode(result));
}

#[test]
fn test_decode() {
let components = vec![
PathComponent::new(Some(44), true).unwrap(),
PathComponent::new(Some(784), true).unwrap(),
PathComponent::new(Some(0), true).unwrap(),
PathComponent::new(Some(0), true).unwrap(),
PathComponent::new(Some(0), true).unwrap(),
];
let source_fingerprint = hex::decode("78230804").unwrap().try_into().unwrap();
let crypto_key_path = CryptoKeyPath::new(components, Some(source_fingerprint), None);
let expect_result = SuiSignHashRequest {
request_id: Some(hex::decode("9b1deb4d3b7d4bad9bdd2b0d7b3dcb6d").unwrap()),
message_hash:"00000000000200201ff915a5e9e32fdbe0135535b6c69a00a9809aaf7f7c0275d3239ca79db20d6400081027000000000000020200010101000101020000010000ebe623e33b7307f1350f8934beb3fb16baef0fc1b3f1b92868eec3944093886901a2e3e42930675d9571a467eb5d4b22553c93ccb84e9097972e02c490b4e7a22ab73200000000000020176c4727433105da34209f04ac3f22e192a2573d7948cb2fabde7d13a7f4f149ebe623e33b7307f1350f8934beb3fb16baef0fc1b3f1b92868eec39440938869e803000000000000640000000000000000".to_string(),
derivation_paths: vec![crypto_key_path],
addresses: Some(vec![hex::decode("ebe623e33b7307f1350f8934beb3fb16baef0fc1b3f1b92868eec39440938869").unwrap()]),
origin: Some("Sui Wallet".to_string())
};
let result = SuiSignHashRequest::try_from(hex::decode("a501d825509b1deb4d3b7d4bad9bdd2b0d7b3dcb6d027901b830303030303030303030303230303230316666393135613565396533326664626530313335353335623663363961303061393830396161663766376330323735643332333963613739646232306436343030303831303237303030303030303030303030303230323030303130313031303030313031303230303030303130303030656265363233653333623733303766313335306638393334626562336662313662616566306663316233663162393238363865656333393434303933383836393031613265336534323933303637356439353731613436376562356434623232353533633933636362383465393039373937326530326334393062346537613232616237333230303030303030303030303032303137366334373237343333313035646133343230396630346163336632326531393261323537336437393438636232666162646537643133613766346631343965626536323365333362373330376631333530663839333462656233666231366261656630666331623366316239323836386565633339343430393338383639653830333030303030303030303030303634303030303030303030303030303030300381d90130a2018a182cf5190310f500f500f500f5021a7823080404815820ebe623e33b7307f1350f8934beb3fb16baef0fc1b3f1b92868eec39440938869056a5375692057616c6c6574").unwrap()).unwrap();

assert_eq!(expect_result.request_id, result.request_id);
assert_eq!(expect_result.message_hash, result.message_hash);
assert_eq!(
expect_result.derivation_paths[0].get_path(),
result.derivation_paths[0].get_path()
);
assert_eq!(expect_result.addresses, result.addresses);
assert_eq!(expect_result.origin, result.origin);
}
}

0 comments on commit 60c8c07

Please sign in to comment.