feat: support babylon derive context hash
What changed, and why it matters
This commit adds a new feature to the Keystone 3 hardware wallet that lets approved Bitcoin apps (like Babylon) request a special cryptographic hash derived from the wallet's seed. The wallet shows the user the connected Bitcoin address and, after approval, returns a 32-byte hash. The code includes checks to limit which apps can ask, which Bitcoin networks are accepted, and which derivation paths can be used. It also adds a testnet display toggle for Bitcoin receive addresses. There is no direct evidence in the commit that this fixes a known security vulnerability; it appears to be a feature addition.
Treat this as a feature commit rather than an emergency security patch. Reviewers should verify that the address-display verification cannot be bypassed, that the app allow-list and path validation are enforced identically in both the parser and the generator, and that the UI always shows the same address that is verified. Users on multi-coins firmware should ensure they only approve `derive-context-hash` requests from trusted Babylon applications and carefully compare the displayed Bitcoin address before confirming.
Security signals we found
New cryptographic derivation path introduced at m/73681862' for Babylon context hashing
App-name allow-list enforced for derive-context-hash requests
Derivation path restricted to cached BTC account xpub address suffixes
Displayed address verified against seed-derived address before hash generation
Seed copied into Rust vector and zeroized after use
New UI view for user approval of derive-context-hash requests
Testnet address display toggle added to Bitcoin receive page
Version bump from build 4 to 6
Evidence from the diff
The patch implements the Babylon deriveContextHash protocol: a new app_babylon Rust crate computes HKDF-SHA-256 over a BIP-32 private key at m/73681862', with info built from SHA-256(appName), SHA-256(network), connectedPubkey, and context. A new FFI module rust/rust_c/src/wallet/babylon.rs decodes qr-hardware-call UR requests, validates an allow-listed app name (babylon-btc-vault, test-app), validates the network and context hex, restricts derivation paths to cached BTC account xpub suffixes (M/44'/0'/0', 49, 84, 86 + change/index), verifies the displayed address matches the seed-derived address, and returns the hash as a Bytes UR. UI wiring adds a new DeriveContextHashRequest view. A get_address_with_network helper and a testnet address display toggle are also added. The implementation includes unit tests and zeroizes the seed copy after use.
Changed components
rust/apps/babylon (new)rust/rust_c/src/wallet/babylon.rs (new)rust/rust_c/src/common/ur.rsrust/rust_c/src/common/ur_ext.rsrust/rust_c/src/wallet/structs.rsrust/apps/bitcoin/src/addresses/mod.rssrc/ui/gui_resolve_ur.csrc/ui/gui_views/multi/gui_derive_context_hash_request_view.c (new)src/ui/gui_widgets/multi/gui_derive_context_hash_request_widgets.c (new)src/ui/gui_widgets/gui_utxo_receive_widgets.csrc/config/version.hInspect captured patch +1949 / −25
diff --git a/images/coin/coinBtcTestnet.png b/images/coin/coinBtcTestnet.png
new file mode 100644
index 0000000..6795a5b
Binary files /dev/null and b/images/coin/coinBtcTestnet.png differ
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index 1e720bd..ed58114 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -190,6 +190,17 @@ dependencies = [
"ur-registry",
]
+[[package]]
+name = "app_babylon"
+version = "0.1.0"
+dependencies = [
+ "cryptoxide",
+ "hex",
+ "keystore",
+ "thiserror-core",
+ "zeroize",
+]
+
[[package]]
name = "app_bitcoin"
version = "0.1.0"
@@ -2527,7 +2538,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
- "windows-targets 0.53.2",
+ "windows-targets 0.48.5",
]
[[package]]
@@ -3807,6 +3818,7 @@ dependencies = [
"app_aptos",
"app_arweave",
"app_avalanche",
+ "app_babylon",
"app_bitcoin",
"app_cardano",
"app_cosmos",
@@ -4759,9 +4771,9 @@ dependencies = [
[[package]]
name = "ur-registry"
-version = "1.0.4"
+version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9b75e98b4ddfd7140efcf02b4e5d420e91f14cb7a30623c7aa83dccac1c7f64"
+checksum = "1c09637b609337d361420befd9a218a0ebf6923c10a56b8d3c895419973cd71c"
dependencies = [
"bs58",
"hex",
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 6cc914e..c3c8e61 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -20,6 +20,7 @@ members = [
"apps/zcash",
"apps/avalanche",
"apps/iota",
+ "apps/babylon",
# C interface entry
"rust_c",
@@ -51,6 +52,7 @@ app_xrp = { path = "apps/xrp" }
app_zcash = { path = "apps/zcash", default-features = false}
app_monero = { path = "apps/monero" }
app_iota = { path = "apps/iota" }
+app_babylon = { path = "apps/babylon" }
keystore = { path = "keystore", default-features = false }
tools = { path = "tools" }
sim_qr_reader = { path = "sim_qr_reader" }
@@ -74,7 +76,7 @@ core2 = { version = "0.9.4", package = "no_std_io2", default-features = false }
thiserror = { version = "1.0", package = "thiserror-core", default-features = false }
rsa = { version = "0.8.2", default-features = false }
sha1 = { version = "0.10.5", default-features = false }
-ur-registry = "1.0.4"
+ur-registry = "1.0.5"
ur-parse-lib = "1.0.4"
sui-transaction-types-core = { git = "https://github.com/KeystoneHQ/sui.git", tag = "mainnet-nostd-v1.69.2.1", default-features = false, features = ["alloc"] }
ed25519-bip32-core = { version = "0.1.1", default-features = false }
diff --git a/rust/apps/babylon/Cargo.toml b/rust/apps/babylon/Cargo.toml
new file mode 100644
index 0000000..d5a53fb
--- /dev/null
+++ b/rust/apps/babylon/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "app_babylon"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+hex = { workspace = true }
+thiserror = { workspace = true }
+cryptoxide = { workspace = true }
+zeroize = { workspace = true }
+
+[dev-dependencies]
+keystore = { workspace = true, features = ["multi_coins"] }
+
+[lints.rust]
+unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] }
diff --git a/rust/apps/babylon/src/errors.rs b/rust/apps/babylon/src/errors.rs
new file mode 100644
index 0000000..b36bbfa
--- /dev/null
+++ b/rust/apps/babylon/src/errors.rs
@@ -0,0 +1,18 @@
+use alloc::string::String;
+use thiserror::Error;
+
+pub type Result<T> = core::result::Result<T, BabylonError>;
+
+#[derive(Error, Debug, Clone, PartialEq, Eq)]
+pub enum BabylonError {
+ #[error("app not allowed: {0}")]
+ AppNotAllowed(String),
+ #[error("invalid app name: {0}")]
+ InvalidAppName(String),
+ #[error("invalid context: {0}")]
+ InvalidContext(String),
+ #[error("unsupported network: {0}")]
+ UnsupportedNetwork(String),
+ #[error("derivation failed: {0}")]
+ DeriveError(String),
+}
diff --git a/rust/apps/babylon/src/lib.rs b/rust/apps/babylon/src/lib.rs
new file mode 100644
index 0000000..5644620
--- /dev/null
+++ b/rust/apps/babylon/src/lib.rs
@@ -0,0 +1,407 @@
+//! Babylon `deriveContextHash` core logic.
+//!
+//! Implements the wallet-side derivation described in
+//! <https://github.com/babylonlabs-io/babylon-toolkit/blob/main/docs/specs/derive-context-hash.md>
+//!
+//! ```text
+//! ikm = secp256k1 BIP-32 private key (32-byte scalar) at m/73681862'
+//! salt = "derive-context-hash"
+//! info = SHA-256(appName) || SHA-256(networkName) || connectedPubkey(33) || context
+//! output = HKDF-SHA-256(ikm, salt, info, 32)
+//! ```
+//!
+//! This crate is the pure cryptographic core (no FFI, no UR, no seed access). The
+//! `ikm` and `connectedPubkey` are derived by the caller (the FFI layer) and passed
+//! in as bytes. Input validation helpers live here so they can be unit-tested in
+//! isolation; the appName allow-list is a device policy enforced by these helpers
+//! and by the FFI request handler, NOT by [`derive_context_hash`] itself.
+
+#![no_std]
+
+#[macro_use]
+extern crate alloc;
+
+pub mod errors;
+
+use alloc::string::ToString;
+use alloc::vec::Vec;
+
+use cryptoxide::digest::Digest;
+use cryptoxide::hkdf::{hkdf_expand, hkdf_extract};
+use cryptoxide::sha2::Sha256;
+use zeroize::Zeroize;
+
+use crate::errors::{BabylonError, Result};
+
+/// Hardened BIP-32 purpose index used to derive the IKM.
+///
+/// Equals `trunc31_be(SHA-256("derive-context-hash"))` — see `test_purpose_index`.
+pub const DERIVE_CONTEXT_HASH_PURPOSE: u32 = 73681862;
+
+/// BIP-32 path (a single hardened child of the secp256k1 master) for the IKM.
+pub const IKM_DERIVATION_PATH: &str = "m/73681862'";
+
+/// HKDF salt providing domain separation (RFC 5869).
+const SALT: &[u8] = b"derive-context-hash";
+
+/// Output length in bytes.
+pub const OUTPUT_LEN: usize = 32;
+
+/// Maximum `appName` length in bytes.
+pub const APP_NAME_MAX_LEN: usize = 64;
+
+/// Maximum decoded `context` length in bytes.
+pub const CONTEXT_MAX_LEN: usize = 1024;
+
+/// Apps allowed to request a context hash on this device (device policy).
+pub const ALLOWED_APP_NAMES: [&str; 2] = ["babylon-btc-vault", "test-app"];
+
+/// The wallet's current Bitcoin network (canonical name per the spec).
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum BabylonNetwork {
+ Mainnet,
+ Testnet,
+ Signet,
+ Regtest,
+}
+
+impl BabylonNetwork {
+ /// The canonical network name hashed into `info`.
+ pub const fn canonical_name(&self) -> &'static str {
+ match self {
+ BabylonNetwork::Mainnet => "bitcoin-mainnet",
+ BabylonNetwork::Testnet => "bitcoin-testnet",
+ BabylonNetwork::Signet => "bitcoin-signet",
+ BabylonNetwork::Regtest => "bitcoin-regtest",
+ }
+ }
+
+ /// Parse a canonical network name; unknown names are rejected (no fallback).
+ pub fn from_name(name: &str) -> Result<Self> {
+ match name {
+ "bitcoin-mainnet" => Ok(BabylonNetwork::Mainnet),
+ "bitcoin-testnet" => Ok(BabylonNetwork::Testnet),
+ "bitcoin-signet" => Ok(BabylonNetwork::Signet),
+ "bitcoin-regtest" => Ok(BabylonNetwork::Regtest),
+ other => Err(BabylonError::UnsupportedNetwork(other.to_string())),
+ }
+ }
+}
+
+/// Inputs to the pure derivation. All values are assumed already derived/decoded.
+pub struct DeriveContextHashInput<'a> {
+ /// Raw 32-byte private scalar at `m/73681862'`.
+ pub ikm: [u8; 32],
+ /// Human-readable app identifier (domain separation).
+ pub app_name: &'a str,
+ /// Wallet's current Bitcoin network.
+ pub network: BabylonNetwork,
+ /// 33-byte compressed SEC1 public key of the connected key.
+ pub connected_pubkey: [u8; 33],
+ /// Application-specific context (already hex-decoded).
+ pub context: &'a [u8],
+}
+
+fn sha256(data: &[u8]) -> [u8; 32] {
+ let mut hasher = Sha256::new();
+ let mut out = [0u8; 32];
+ hasher.input(data);
+ hasher.result(&mut out);
+ out
+}
+
+/// Compute the 32-byte context hash per the Babylon `deriveContextHash` spec.
+///
+/// This is the pure cryptographic core: it enforces structural `context` bounds but
+/// does NOT enforce the appName allow-list (a device policy applied one layer up).
+pub fn derive_context_hash(input: &DeriveContextHashInput) -> Result<[u8; OUTPUT_LEN]> {
+ if input.context.is_empty() {
+ return Err(BabylonError::InvalidContext(
+ "context must not be empty".to_string(),
+ ));
+ }
+ if input.context.len() > CONTEXT_MAX_LEN {
+ return Err(BabylonError::InvalidContext(format!(
+ "context exceeds {CONTEXT_MAX_LEN} bytes"
+ )));
+ }
+
+ // info = SHA-256(appName) || SHA-256(networkName) || connectedPubkey || context
+ let mut info = Vec::with_capacity(32 + 32 + 33 + input.context.len());
+ info.extend_from_slice(&sha256(input.app_name.as_bytes()));
+ info.extend_from_slice(&sha256(input.network.canonical_name().as_bytes()));
+ info.extend_from_slice(&input.connected_pubkey);
+ info.extend_from_slice(input.context);
+
+ // HKDF-SHA-256 (RFC 5869): extract then expand.
+ let mut prk = [0u8; 32];
+ hkdf_extract(Sha256::new(), SALT, &input.ikm, &mut prk);
+
+ let mut okm = [0u8; OUTPUT_LEN];
+ hkdf_expand(Sha256::new(), &prk, &info, &mut okm);
+
+ prk.zeroize();
+ Ok(okm)
+}
+
+/// Validate `appName`: length `1..=64`, charset `[a-z0-9-]`, AND membership in the
+/// device allow-list ([`ALLOWED_APP_NAMES`]).
+pub fn validate_app_name(app_name: &str) -> Result<()> {
+ let len = app_name.len();
+ if len == 0 || len > APP_NAME_MAX_LEN {
+ return Err(BabylonError::InvalidAppName(format!(
+ "app name length must be 1..={APP_NAME_MAX_LEN}, got {len}"
+ )));
+ }
+ if !app_name
+ .bytes()
+ .all(|b| matches!(b, b'a'..=b'z' | b'0'..=b'9' | b'-'))
+ {
+ return Err(BabylonError::InvalidAppName(
+ "app name must match [a-z0-9-]".to_string(),
+ ));
+ }
+ if !ALLOWED_APP_NAMES.contains(&app_name) {
+ return Err(BabylonError::AppNotAllowed(app_name.to_string()));
+ }
+ Ok(())
+}
+
+/// Validate and decode the `context` hex string.
+///
+/// Rules: non-empty, even length, lowercase hex with no `0x` prefix, and a decoded
+/// length of at most [`CONTEXT_MAX_LEN`] bytes.
+pub fn validate_context_hex(context_hex: &str) -> Result<Vec<u8>> {
+ if context_hex.is_empty() {
+ return Err(BabylonError::InvalidContext(
+ "context must not be empty".to_string(),
+ ));
+ }
+ if context_hex.len() % 2 != 0 {
+ return Err(BabylonError::InvalidContext(
+ "context hex must be even length".to_string(),
+ ));
+ }
+ if context_hex
+ .bytes()
+ .any(|b| !matches!(b, b'0'..=b'9' | b'a'..=b'f'))
+ {
+ return Err(BabylonError::InvalidContext(
+ "context must be lowercase hex with no 0x prefix".to_string(),
+ ));
+ }
+ let bytes = hex::decode(context_hex)
+ .map_err(|e| BabylonError::InvalidContext(format!("invalid hex: {e}")))?;
+ if bytes.len() > CONTEXT_MAX_LEN {
+ return Err(BabylonError::InvalidContext(format!(
+ "context exceeds {CONTEXT_MAX_LEN} bytes"
+ )));
+ }
+ Ok(bytes)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use alloc::string::ToString;
+ use keystore::algorithms::secp256k1::{get_private_key_by_seed, get_public_key_by_seed};
+
+ // BIP-39 seed for "abandon abandon abandon abandon abandon abandon abandon
+ // abandon abandon abandon abandon about" with an empty passphrase.
+ const TEST_SEED_HEX: &str = "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4";
+
+ #[test]
+ fn test_official_vector() {
+ let seed = hex::decode(TEST_SEED_HEX).unwrap();
+
+ let ikm = get_private_key_by_seed(&seed, &IKM_DERIVATION_PATH.to_string())
+ .unwrap()
+ .secret_bytes();
+ let connected_pubkey = get_public_key_by_seed(&seed, &"m/44'/0'/0'/0/0".to_string())
+ .unwrap()
+ .serialize();
+ let context = hex::decode("deadbeef").unwrap();
+
+ let input = DeriveContextHashInput {
+ ikm,
+ app_name: "test-app",
+ network: BabylonNetwork::Mainnet,
+ connected_pubkey,
+ context: &context,
+ };
+
+ let out = derive_context_hash(&input).unwrap();
+ assert_eq!(
+ hex::encode(out),
+ "f82ced3be0e29591a7863ece03d65f79fb494fe0de7203549855f462455df008"
+ );
+ }
+
+ #[test]
+ fn test_purpose_index() {
+ // 73681862 == trunc31_be(SHA-256("derive-context-hash"))
+ let h = sha256(b"derive-context-hash");
+ let be = u32::from_be_bytes([h[0], h[1], h[2], h[3]]);
+ let trunc31 = be & 0x7FFF_FFFF;
+ assert_eq!(trunc31, DERIVE_CONTEXT_HASH_PURPOSE);
+ }
+
+ #[test]
+ fn test_info_layout_and_determinism() {
+ let input = DeriveContextHashInput {
+ ikm: [7u8; 32],
+ app_name: "babylon-btc-vault",
+ network: BabylonNetwork::Signet,
+ connected_pubkey: [0x02; 33],
+ context: &[0xde, 0xad],
+ };
+ let a = derive_context_hash(&input).unwrap();
+ let b = derive_context_hash(&input).unwrap();
+ assert_eq!(a, b, "derivation must be deterministic");
+ assert_eq!(a.len(), OUTPUT_LEN);
+ }
+
+ #[test]
+ fn test_context_changes_output() {
+ let base = DeriveContextHashInput {
+ ikm: [7u8; 32],
+ app_name: "babylon-btc-vault",
+ network: BabylonNetwork::Mainnet,
+ connected_pubkey: [0x02; 33],
+ context: &[0x01],
+ };
+ let other = DeriveContextHashInput {
+ context: &[0x02],
+ ..DeriveContextHashInput {
+ ikm: base.ikm,
+ app_name: base.app_name,
+ network: base.network,
+ connected_pubkey: base.connected_pubkey,
+ context: base.context,
+ }
+ };
+ assert_ne!(
+ derive_context_hash(&base).unwrap(),
+ derive_context_hash(&other).unwrap()
+ );
+ }
+
+ #[test]
+ fn test_derive_rejects_empty_context() {
+ let input = DeriveContextHashInput {
+ ikm: [1u8; 32],
+ app_name: "babylon-btc-vault",
+ network: BabylonNetwork::Mainnet,
+ connected_pubkey: [2u8; 33],
+ context: &[],
+ };
+ assert!(matches!(
+ derive_context_hash(&input),
+ Err(BabylonError::InvalidContext(_))
+ ));
+ }
+
+ #[test]
+ fn test_derive_rejects_oversized_context() {
+ let context = vec![0u8; CONTEXT_MAX_LEN + 1];
+ let input = DeriveContextHashInput {
+ ikm: [1u8; 32],
+ app_name: "babylon-btc-vault",
+ network: BabylonNetwork::Mainnet,
+ connected_pubkey: [2u8; 33],
+ context: &context,
+ };
+ assert!(matches!(
+ derive_context_hash(&input),
+ Err(BabylonError::InvalidContext(_))
+ ));
+ }
+
+ #[test]
+ fn test_validate_app_name() {
+ assert!(validate_app_name("babylon-btc-vault").is_ok());
+ assert!(validate_app_name("test-app").is_ok());
+ // Uppercase -> charset failure.
+ assert!(matches!(
+ validate_app_name("Babylon"),
+ Err(BabylonError::InvalidAppName(_))
+ ));
+ // Empty.
+ assert!(matches!(
+ validate_app_name(""),
+ Err(BabylonError::InvalidAppName(_))
+ ));
+ // Too long.
+ let long = "a".repeat(APP_NAME_MAX_LEN + 1);
+ assert!(matches!(
+ validate_app_name(&long),
+ Err(BabylonError::InvalidAppName(_))
+ ));
+ }
+
+ #[test]
+ fn test_validate_context_hex() {
+ assert_eq!(
+ validate_context_hex("deadbeef").unwrap(),
+ vec![0xde, 0xad, 0xbe, 0xef]
+ );
+ assert!(matches!(
+ validate_context_hex(""),
+ Err(BabylonError::InvalidContext(_))
+ ));
+ // Odd length.
+ assert!(matches!(
+ validate_context_hex("abc"),
+ Err(BabylonError::InvalidContext(_))
+ ));
+ // Uppercase.
+ assert!(matches!(
+ validate_context_hex("DEADBEEF"),
+ Err(BabylonError::InvalidContext(_))
+ ));
+ // 0x prefix (the 'x' is not lowercase hex).
+ assert!(matches!(
+ validate_context_hex("0xdeadbeef"),
+ Err(BabylonError::InvalidContext(_))
+ ));
+ // Decodes to 1025 bytes (> 1024).
+ let big = "ab".repeat(CONTEXT_MAX_LEN + 1);
+ assert!(matches!(
+ validate_context_hex(&big),
+ Err(BabylonError::InvalidContext(_))
+ ));
+ }
+
+ #[test]
+ fn test_network_from_name() {
+ assert_eq!(
+ BabylonNetwork::from_name("bitcoin-mainnet").unwrap(),
+ BabylonNetwork::Mainnet
+ );
+ assert_eq!(
+ BabylonNetwork::from_name("bitcoin-testnet").unwrap(),
+ BabylonNetwork::Testnet
+ );
+ assert_eq!(
+ BabylonNetwork::from_name("bitcoin-signet").unwrap(),
+ BabylonNetwork::Signet
+ );
+ assert_eq!(
+ BabylonNetwork::from_name("bitcoin-regtest").unwrap(),
+ BabylonNetwork::Regtest
+ );
+ assert!(matches!(
+ BabylonNetwork::from_name("ethereum"),
+ Err(BabylonError::UnsupportedNetwork(_))
+ ));
+ // Round-trip every variant.
+ for n in [
+ BabylonNetwork::Mainnet,
+ BabylonNetwork::Testnet,
+ BabylonNetwork::Signet,
+ BabylonNetwork::Regtest,
+ ] {
+ assert_eq!(BabylonNetwork::from_name(n.canonical_name()).unwrap(), n);
+ }
+ }
+}
diff --git a/rust/apps/bitcoin/src/addresses/mod.rs b/rust/apps/bitcoin/src/addresses/mod.rs
index 72fe362..9240147 100644
--- a/rust/apps/bitcoin/src/addresses/mod.rs
+++ b/rust/apps/bitcoin/src/addresses/mod.rs
@@ -53,6 +53,31 @@ pub fn get_address(hd_path: String, extended_pub_key: &String) -> Result<String>
Ok(address.to_string())
}
+pub fn get_address_with_network(
+ hd_path: String,
+ extended_pub_key: &String,
+ network: Network,
+) -> Result<String> {
+ let account_hd_path = derivation_account_path!(hd_path)?;
+ let address_hd_path = derivation_address_path!(hd_path)?;
+ let compressed_ecdsa_pubkey = derive_public_key(extended_pub_key, address_hd_path)?;
+ let purpose = account_hd_path
+ .split('/')
+ .nth(1)
+ .and_then(|v| v.trim_end_matches('\'').parse::<u32>().ok())
+ .ok_or_else(|| BitcoinError::AddressError("invalid purpose".to_string()))?;
+ let address = match purpose {
+ 44 => Address::p2pkh(&compressed_ecdsa_pubkey, network),
+ 49 => Address::p2shp2wpkh(&compressed_ecdsa_pubkey, network),
+ 84 => Address::p2wpkh(&compressed_ecdsa_pubkey, network),
+ 86 => Address::p2tr_no_script(&compressed_ecdsa_pubkey, network),
+ _ => Err(BitcoinError::AddressError(
+ "network is not supported".to_string(),
+ )),
+ }?;
+ Ok(address.to_string())
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -120,6 +145,69 @@ mod tests {
assert_eq!(address, "tb1q6sjunnh9w9epn9z7he2dxmklgfg7x38yefmld7")
}
+ #[test]
+ fn test_get_address_with_network_uses_explicit_network() {
+ let extended_pubkey = "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs";
+ let address = get_address_with_network(
+ String::from("M/84'/0'/0'/0/0"),
+ &extended_pubkey.to_string(),
+ Network::BitcoinTestnet,
+ )
+ .unwrap();
+ assert!(address.starts_with("tb1q"));
+ }
+
+ #[test]
+ fn test_get_address_with_network_uses_testnet_with_xpub_p2pkh() {
+ let extended_pubkey = "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj";
+ let address = get_address_with_network(
+ String::from("M/44'/0'/0'/0/0"),
+ &extended_pubkey.to_string(),
+ Network::BitcoinTestnet,
+ )
+ .unwrap();
+ assert_eq!(address, "n1M8ZVQtL7QoFvGMg24D6b2ojWvFXCGpoS");
+ }
+
+ #[test]
+ fn test_get_address_with_network_uses_testnet_with_xpub_p2sh_p2wpkh() {
+ let extended_pubkey = "ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP";
+ let address = get_address_with_network(
+ String::from("M/49'/0'/0'/0/0"),
+ &extended_pubkey.to_string(),
+ Network::BitcoinTestnet,
+ )
+ .unwrap();
+ assert_eq!(address, "2My47gHNc8nhX5kBWqXHU4f8uuQvQKEgwMd");
+ }
+
+ #[test]
+ fn test_get_address_with_network_uses_testnet_with_xpub_p2wpkh() {
+ let extended_pubkey = "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs";
+ let address = get_address_with_network(
+ String::from("M/84'/0'/0'/0/0"),
+ &extended_pubkey.to_string(),
+ Network::BitcoinTestnet,
+ )
+ .unwrap();
+ assert_eq!(address, "tb1qcr8te4kr609gcawutmrza0j4xv80jy8zmfp6l0");
+ }
+
+ #[test]
+ fn test_get_address_with_network_uses_testnet_with_xpub_p2tr() {
+ let extended_pubkey = "xpub6Cq9mdT8xwFe9LYQnt9y1hJXTyo7KQJM8pRH6K95F1mbELzgm825m3hyAZ97vsUV8Xh7VRwu7bKuLZEmUV1ABqCRQqFzZHAsfaJXTYSY1cf";
+ let address = get_address_with_network(
+ String::from("M/86'/0'/0'/0/0"),
+ &extended_pubkey.to_string(),
+ Network::BitcoinTestnet,
+ )
+ .unwrap();
+ assert_eq!(
+ address,
+ "tb1pf7al7fmsrfxftwp0durgv3zv9ezqzrqdhk34gjcuqasw6fnlxu7se0srgl"
+ );
+ }
+
#[test]
fn test_bch_p2pkh_address() {
let extended_pubkey = "xpub6ByHsPNSQXTWZ7PLESMY2FufyYWtLXagSUpMQq7Un96SiThZH2iJB1X7pwviH1WtKVeDP6K8d6xxFzzoaFzF3s8BKCZx8oEDdDkNnp4owAZ";
diff --git a/rust/apps/bitcoin/src/lib.rs b/rust/apps/bitcoin/src/lib.rs
index cc37a99..28355b6 100644
--- a/rust/apps/bitcoin/src/lib.rs
+++ b/rust/apps/bitcoin/src/lib.rs
@@ -12,6 +12,7 @@ use alloc::string::{String, ToString};
use alloc::vec::Vec;
pub use addresses::get_address;
+pub use addresses::get_address_with_network;
use app_utils::keystone;
use bitcoin::bip32::Fingerprint;
use bitcoin::hashes::Hash;
diff --git a/rust/rust_c/Cargo.toml b/rust/rust_c/Cargo.toml
index 4d56b97..ef5fe81 100644
--- a/rust/rust_c/Cargo.toml
+++ b/rust/rust_c/Cargo.toml
@@ -41,6 +41,7 @@ zeroize = { workspace = true }
#apps
app_wallets = { workspace = true }
+app_babylon = { workspace = true, optional = true }
app_bitcoin = { workspace = true, optional = true }
app_ethereum = { workspace = true, optional = true }
app_cardano = { workspace = true, optional = true }
@@ -113,6 +114,7 @@ multi-coins = [
"avalanche",
"iota",
"zcash_multi_coins",
+ "dep:app_babylon",
"keystore/multi_coins",
]
diff --git a/rust/rust_c/src/bitcoin/address.rs b/rust/rust_c/src/bitcoin/address.rs
index 936e1ad..73fac56 100644
--- a/rust/rust_c/src/bitcoin/address.rs
+++ b/rust/rust_c/src/bitcoin/address.rs
@@ -1,8 +1,11 @@
+use alloc::string::{String, ToString};
+
use crate::common::structs::SimpleResponse;
use crate::common::types::PtrString;
use crate::common::utils::{convert_c_char, recover_c_char};
use app_bitcoin;
use app_bitcoin::addresses::xyzpub;
+use app_bitcoin::network::Network;
use bitcoin::secp256k1::ffi::types::c_char;
use core::str::FromStr;
@@ -20,6 +23,26 @@ pub unsafe extern "C" fn utxo_get_address(
}
}
+#[no_mangle]
+pub unsafe extern "C" fn btcoin_get_address_with_network(
+ hd_path: PtrString,
+ x_pub: PtrString,
+ network: PtrString,
+) -> *mut SimpleResponse<c_char> {
+ let x_pub = recover_c_char(x_pub);
+ let hd_path = recover_c_char(hd_path);
+ let network: String = recover_c_char(network);
+ let network = match bitcoin_address_network(network.as_str()) {
+ Ok(v) => v,
+ Err(e) => return SimpleResponse::from(e).simple_c_ptr(),
+ };
+ let address = app_bitcoin::get_address_with_network(hd_path, &x_pub, network);
+ match address {
+ Ok(result) => SimpleResponse::success(convert_c_char(result) as *mut c_char).simple_c_ptr(),
+ Err(e) => SimpleResponse::from(e).simple_c_ptr(),
+ }
+}
+
#[no_mangle]
pub unsafe extern "C" fn xpub_convert_version(
x_pub: PtrString,
@@ -38,3 +61,15 @@ pub unsafe extern "C" fn xpub_convert_version(
Err(e) => SimpleResponse::from(e).simple_c_ptr(),
}
}
+
+fn bitcoin_address_network(network: &str) -> Result<Network, app_bitcoin::errors::BitcoinError> {
+ match network {
+ "bitcoin-mainnet" | "BTC" | "mainnet" => Ok(Network::Bitcoin),
+ "bitcoin-testnet" | "bitcoin-signet" | "tBTC" | "testnet" | "signet" => {
+ Ok(Network::BitcoinTestnet)
+ }
+ other => Err(app_bitcoin::errors::BitcoinError::UnsupportedNetwork(
+ other.to_string(),
+ )),
+ }
+}
diff --git a/rust/rust_c/src/common/ur.rs b/rust/rust_c/src/common/ur.rs
index 7fcc212..4ad15d6 100644
--- a/rust/rust_c/src/common/ur.rs
+++ b/rust/rust_c/src/common/ur.rs
@@ -289,6 +289,8 @@ pub enum ViewType {
#[cfg(not(feature = "btc-only"))]
KeyDerivationRequest,
#[cfg(feature = "multi-coins")]
+ DeriveContextHashRequest,
+ #[cfg(feature = "multi-coins")]
BatchCall,
#[cfg(feature = "btc-only")]
MultisigWalletImport,
diff --git a/rust/rust_c/src/common/ur_ext.rs b/rust/rust_c/src/common/ur_ext.rs
index 3733f5b..695c60a 100644
--- a/rust/rust_c/src/common/ur_ext.rs
+++ b/rust/rust_c/src/common/ur_ext.rs
@@ -421,6 +421,12 @@ impl InferViewType for QRHardwareCall {
fn infer(&self) -> Result<ViewType, URError> {
match self.get_call_type() {
CallType::KeyDerivation => Ok(ViewType::KeyDerivationRequest),
+ #[cfg(feature = "multi-coins")]
+ CallType::DeriveContextHash => Ok(ViewType::DeriveContextHashRequest),
+ #[cfg(not(feature = "multi-coins"))]
+ CallType::DeriveContextHash => Err(URError::NotSupportURTypeError(
+ "derive-context-hash is only supported on multi-coins".to_string(),
+ )),
}
}
}
diff --git a/rust/rust_c/src/wallet/babylon.rs b/rust/rust_c/src/wallet/babylon.rs
new file mode 100644
index 0000000..f21e8f1
--- /dev/null
+++ b/rust/rust_c/src/wallet/babylon.rs
@@ -0,0 +1,556 @@
+//! FFI layer for Babylon `deriveContextHash` (Phases 2 + 4).
+//!
+//! Crypto core lives in `app_babylon`. Transport is the `qr-hardware-call` UR with
+//! `CallParams::DeriveContextHash`. This module decodes the request, derives the
+//! connected address for display, and produces the 32-byte result as a `Bytes` UR.
+
+use alloc::string::{String, ToString};
+
+use app_babylon::errors::BabylonError;
+use app_babylon::{
+ derive_context_hash, validate_app_name, validate_context_hex, BabylonNetwork,
+ DeriveContextHashInput, IKM_DERIVATION_PATH,
+};
+use bitcoin::key::UntweakedPublicKey;
+use bitcoin::secp256k1::Secp256k1;
+use bitcoin::{Address, CompressedPublicKey, Network as BitcoinNetwork, PublicKey};
+use keystore::algorithms::secp256k1::{get_private_key_by_seed, get_public_key_by_seed};
+use ur_registry::bytes::Bytes;
+use ur_registry::extend::qr_hardware_call::{CallParams, QRHardwareCall};
+use ur_registry::traits::RegistryItem;
+use zeroize::Zeroize;
+
+use crate::common::errors::RustCError;
+use crate::common::free::Free;
+use crate::common::structs::Response;
+use crate::common::types::{PtrBytes, PtrString, PtrT, PtrUR};
+use crate::common::ur::{UREncodeResult, FRAGMENT_MAX_LENGTH_DEFAULT};
+use crate::common::utils::{convert_c_char, recover_c_char};
+use crate::{extract_ptr_with_type, free_str_ptr, impl_c_ptr, make_free_method};
+
+impl From<BabylonError> for RustCError {
+ fn from(value: BabylonError) -> Self {
+ RustCError::InvalidData(value.to_string())
+ }
+}
+
+/// Fields decoded from a `derive-context-hash` qr-hardware-call.
+struct DecodedRequest {
+ app_name: String,
+ network: String,
+ key_path: String,
+ context: String,
+ origin: Option<String>,
+}
+
+/// # Safety
+/// `ur` must point to a valid `QRHardwareCall`.
+unsafe fn decode_request(ur: PtrUR) -> Result<DecodedRequest, RustCError> {
+ let call = extract_ptr_with_type!(ur, QRHardwareCall);
+ match call.get_params() {
+ CallParams::DeriveContextHash(d) => {
+ let key_path = d
+ .get_key_path()
+ .get_path()
+ .ok_or(RustCError::InvalidData("missing key path".to_string()))?;
+ Ok(DecodedRequest {
+ app_name: d.get_app_name(),
+ network: d.get_network(),
+ key_path,
+ context: d.get_context(),
+ origin: call.get_origin(),
+ })
+ }
+ _ => Err(RustCError::InvalidData(
+ "not a derive-context-hash call".to_string(),
+ )),
+ }
+}
+
+/// Display data shown on the approval screen (no secret material).
+#[repr(C)]
+pub struct DeriveContextHashCallData {
+ pub app_name: PtrString,
+ pub network: PtrString,
+ pub key_path: PtrString,
+ pub context: PtrString,
+ pub origin: PtrString,
+}
+
+impl_c_ptr!(DeriveContextHashCallData);
+
+impl Free for DeriveContextHashCallData {
+ unsafe fn free(&self) {
+ free_str_ptr!(self.app_name);
+ free_str_ptr!(self.network);
+ free_str_ptr!(self.key_path);
+ free_str_ptr!(self.context);
+ free_str_ptr!(self.origin);
+ }
+}
+
+make_free_method!(Response<DeriveContextHashCallData>);
+
+/// Decode + validate the request and produce display data for the approval screen.
+///
+/// Rejects disallowed appNames and unsupported networks *before* the screen is
+/// shown. The UI derives the display address from the cached account xpub after
+/// this validation, using the same validated network and key path.
+///
+/// # Safety
+/// `ur` must point to a valid `QRHardwareCall`.
+#[no_mangle]
+pub unsafe extern "C" fn parse_derive_context_hash(
+ ur: PtrUR,
+) -> PtrT<Response<DeriveContextHashCallData>> {
+ let req = match decode_request(ur) {
+ Ok(v) => v,
+ Err(e) => return Response::from(e).c_ptr(),
+ };
+
+ if let Err(e) = validate_app_name(&req.app_name) {
+ return Response::from(RustCError::from(e)).c_ptr();
+ }
+ if let Err(e) = BabylonNetwork::from_name(&req.network) {
+ return Response::from(RustCError::from(e)).c_ptr();
+ }
+ if let Err(e) = validate_context_hex(&req.context) {
+ return Response::from(RustCError::from(e)).c_ptr();
+ }
+ if let Err(e) = validate_cached_xpub_path(&req.network, &req.key_path) {
+ return Response::from(e).c_ptr();
+ }
+
+ let data = DeriveContextHashCallData {
+ app_name: convert_c_char(req.app_name),
+ network: convert_c_char(req.network),
+ key_path: convert_c_char(req.key_path),
+ context: convert_c_char(req.context),
+ origin: match req.origin {
+ Some(o) => convert_c_char(o),
+ None => core::ptr::null_mut(),
+ },
+ };
+ Response::success(data).c_ptr()
+}
+
+/// Run the full derivation and return the 32-byte result as a hex `Bytes` UR.
+///
+/// # Safety
+/// `ur` must point to a valid `QRHardwareCall`; `seed` must be valid for `seed_len` bytes;
+/// `displayed_address` must point to the address shown to the user on the approval screen.
+#[no_mangle]
+pub unsafe extern "C" fn generate_derive_context_hash_ur(
+ ur: PtrUR,
+ seed: PtrBytes,
+ seed_len: u32,
+ displayed_address: PtrString,
+) -> PtrT<UREncodeResult> {
+ let req = match decode_request(ur) {
+ Ok(v) => v,
+ Err(e) => return UREncodeResult::from(e).c_ptr(),
+ };
+ if let Err(e) = validate_cached_xpub_path(&req.network, &req.key_path) {
+ return UREncodeResult::from(e).c_ptr();
+ }
+ let mut seed = core::slice::from_raw_parts(seed, seed_len as usize).to_vec();
+ if displayed_address.is_null() {
+ seed.zeroize();
+ return UREncodeResult::from(RustCError::InvalidData(
+ "missing displayed address".to_string(),
+ ))
+ .c_ptr();
+ }
+ let displayed_address = recover_c_char(displayed_address);
+
+ let result =
+ match verify_displayed_address(&seed, &req.network, &req.key_path, &displayed_address) {
+ Ok(()) => compute_context_hash_hex(
+ &seed,
+ &req.app_name,
+ &req.network,
+ &req.key_path,
+ &req.context,
+ )
+ .map_err(RustCError::from),
+ Err(e) => Err(e),
+ };
+ seed.zeroize();
+
+ let hash_hex = match result {
+ Ok(v) => v,
+ Err(e) => return UREncodeResult::from(e).c_ptr(),
+ };
+
+ let hash_bytes = match hex::decode(hash_hex) {
+ Ok(v) => v,
+ Err(e) => return UREncodeResult::from(RustCError::InvalidHex(e.to_string())).c_ptr(),
+ };
+ match Bytes::new(hash_bytes).try_into() {
+ Ok(cbor) => UREncodeResult::encode(
+ cbor,
+ Bytes::get_registry_type().get_type(),
+ FRAGMENT_MAX_LENGTH_DEFAULT,
+ )
+ .c_ptr(),
+ Err(e) => UREncodeResult::from(e).c_ptr(),
+ }
+}
+
+/// Core (testable) derivation: seed + request fields -> lowercase hex hash string.
+fn compute_context_hash_hex(
+ seed: &[u8],
+ app_name: &str,
+ network: &str,
+ key_path: &str,
+ context_hex: &str,
+) -> Result<String, BabylonError> {
+ validate_app_name(app_name)?;
+ let network = BabylonNetwork::from_name(network)?;
+ let context = validate_context_hex(context_hex)?;
+
+ let ikm = get_private_key_by_seed(seed, &IKM_DERIVATION_PATH.to_string())
+ .map_err(|e| BabylonError::DeriveError(e.to_string()))?
+ .secret_bytes();
+ let connected_pubkey = get_public_key_by_seed(seed, &normalized_derivation_key_path(key_path))
+ .map_err(|e| BabylonError::DeriveError(e.to_string()))?
+ .serialize();
+
+ let out = derive_context_hash(&DeriveContextHashInput {
+ ikm,
+ app_name,
+ network,
+ connected_pubkey,
+ context: &context,
+ })?;
+ Ok(hex::encode(out))
+}
+
+fn verify_displayed_address(
+ seed: &[u8],
+ network: &str,
+ key_path: &str,
+ displayed_address: &str,
+) -> Result<(), RustCError> {
+ let expected = derive_connected_address_from_seed(seed, network, key_path)?;
+ if expected == displayed_address {
+ return Ok(());
+ }
+ Err(RustCError::InvalidData(
+ "displayed address does not match seed-derived key path".to_string(),
+ ))
+}
+
+fn derive_connected_address_from_seed(
+ seed: &[u8],
+ network: &str,
+ key_path: &str,
+) -> Result<String, RustCError> {
+ let network = BabylonNetwork::from_name(network).map_err(RustCError::from)?;
+ let pubkey = get_public_key_by_seed(seed, &normalized_derivation_key_path(key_path))
+ .map_err(|e| RustCError::InvalidData(e.to_string()))?;
+
+ let btc_network = match network {
+ BabylonNetwork::Mainnet => BitcoinNetwork::Bitcoin,
+ BabylonNetwork::Testnet | BabylonNetwork::Signet | BabylonNetwork::Regtest => {
+ BitcoinNetwork::Testnet
+ }
+ };
+
+ let address = match path_purpose(key_path)? {
+ 44 => Address::p2pkh(PublicKey::new(pubkey).pubkey_hash(), btc_network),
+ 49 => Address::p2shwpkh(&CompressedPublicKey(pubkey), btc_network),
+ 84 => Address::p2wpkh(&CompressedPublicKey(pubkey), btc_network),
+ 86 => {
+ let secp = Secp256k1::verification_only();
+ Address::p2tr(&secp, UntweakedPublicKey::from(pubkey), None, btc_network)
+ }
+ _ => return Err(RustCError::InvalidHDPath),
+ };
+
+ Ok(address.to_string())
+}
+
+/// Extract the BIP-44 purpose (first path component) from a derivation path.
+fn path_purpose(key_path: &str) -> Result<u32, RustCError> {
+ let trimmed = key_path
+ .trim_start_matches("m/")
+ .trim_start_matches("M/")
+ .trim_start_matches('m')
+ .trim_start_matches('M')
+ .trim_start_matches('/');
+ let first = trimmed.split('/').next().unwrap_or("");
+ let digits = first.trim_end_matches(|c| c == '\'' || c == 'h' || c == 'H');
+ digits.parse::<u32>().map_err(|_| RustCError::InvalidHDPath)
+}
+
+fn normalized_derivation_key_path(key_path: &str) -> String {
+ if key_path.starts_with("M/") {
+ let mut path = key_path.to_string();
+ path.replace_range(0..1, "m");
+ path
+ } else {
+ key_path.to_string()
+ }
+}
+
+fn normalized_key_path(key_path: &str) -> String {
+ if key_path.starts_with("m/") || key_path.starts_with("M/") {
+ let mut path = key_path.to_string();
+ path.replace_range(0..1, "M");
+ path
+ } else {
+ alloc::format!("M/{key_path}")
+ }
+}
+
+fn validate_cached_xpub_path(network: &str, key_path: &str) -> Result<(), RustCError> {
+ // The current multi-coins UI displays the connected address from cached BTC
+ // account xpubs. Only accept paths that are under those cached account xpubs.
+ const MAX_DISPLAY_HD_PATH_LEN: usize = 64;
+ let network = BabylonNetwork::from_name(network).map_err(RustCError::from)?;
+ if network == BabylonNetwork::Regtest {
+ return Err(RustCError::InvalidData(
+ "regtest address display is not supported from cached xpub".to_string(),
+ ));
+ }
+
+ const CACHED_XPUB_ACCOUNT_PATHS: [&str; 4] =
+ ["M/44'/0'/0'", "M/49'/0'/0'", "M/84'/0'/0'", "M/86'/0'/0'"];
+ let path = normalized_key_path(key_path);
+ if path.len() >= MAX_DISPLAY_HD_PATH_LEN {
+ return Err(RustCError::InvalidData(
+ "key path is too long for address display".to_string(),
+ ));
+ }
+ if CACHED_XPUB_ACCOUNT_PATHS.iter().any(|account_path| {
+ path.strip_prefix(account_path)
+ .map(is_valid_cached_xpub_address_suffix)
+ .unwrap_or(false)
+ }) {
+ return Ok(());
+ }
+
+ Err(RustCError::InvalidData(
+ "key path is not available from cached xpub".to_string(),
+ ))
+}
+
+fn is_valid_cached_xpub_address_suffix(rest: &str) -> bool {
+ let Some(suffix) = rest.strip_prefix('/') else {
+ return false;
+ };
+ let mut parts = suffix.split('/');
+ let Some(change) = parts.next() else {
+ return false;
+ };
+ let Some(index) = parts.next() else {
+ return false;
+ };
+ parts.next().is_none()
+ && is_non_hardened_child_index(change)
+ && is_non_hardened_child_index(index)
+}
+
+fn is_non_hardened_child_index(component: &str) -> bool {
+ if component.is_empty() || !component.bytes().all(|b| b.is_ascii_digit()) {
+ return false;
+ }
+ component
+ .parse::<u32>()
+ .map(|index| index < 0x8000_0000)
+ .unwrap_or(false)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ extern crate std;
+
+ // BIP-39 seed for "abandon abandon ... about" (empty passphrase).
+ const TEST_SEED_HEX: &str = "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4";
+
+ #[test]
+ fn test_compute_context_hash_hex_official_vector() {
+ let seed = hex::decode(TEST_SEED_HEX).unwrap();
+ // Exercise the crypto path directly; allow-list enforcement is covered separately below.
+ let ikm = get_private_key_by_seed(&seed, &IKM_DERIVATION_PATH.to_string())
+ .unwrap()
+ .secret_bytes();
+ let connected_pubkey = get_public_key_by_seed(&seed, &"m/44'/0'/0'/0/0".to_string())
+ .unwrap()
+ .serialize();
+ let out = derive_context_hash(&DeriveContextHashInput {
+ ikm,
+ app_name: "test-app",
+ network: BabylonNetwork::Mainnet,
+ connected_pubkey,
+ context: &hex::decode("deadbeef").unwrap(),
+ })
+ .unwrap();
+ assert_eq!(
+ hex::encode(out),
+ "f82ced3be0e29591a7863ece03d65f79fb494fe0de7203549855f462455df008"
+ );
+ }
+
+ #[test]
+ fn test_compute_rejects_disallowed_app() {
+ let seed = hex::decode(TEST_SEED_HEX).unwrap();
+ let err = compute_context_hash_hex(
+ &seed,
+ "not-allowed-app",
+ "bitcoin-mainnet",
+ "m/44'/0'/0'/0/0",
+ "deadbeef",
+ )
+ .unwrap_err();
+ assert!(matches!(err, BabylonError::AppNotAllowed(_)));
+ }
+
+ #[test]
+ fn test_compute_allowed_app_succeeds() {
+ let seed = hex::decode(TEST_SEED_HEX).unwrap();
+ let hex_out = compute_context_hash_hex(
+ &seed,
+ "babylon-btc-vault",
+ "bitcoin-mainnet",
+ "m/86'/0'/0'/0/0",
+ "deadbeef",
+ )
+ .unwrap();
+ assert_eq!(hex_out.len(), 64);
+ }
+
+ #[test]
+ fn test_compute_rejects_unsupported_network() {
+ let seed = hex::decode(TEST_SEED_HEX).unwrap();
+ let err = compute_context_hash_hex(
+ &seed,
+ "babylon-btc-vault",
+ "ethereum",
+ "m/86'/0'/0'/0/0",
+ "deadbeef",
+ )
+ .unwrap_err();
+ assert!(matches!(err, BabylonError::UnsupportedNetwork(_)));
+ }
+
+ #[test]
+ fn test_path_purpose() {
+ assert_eq!(path_purpose("m/44'/0'/0'/0/0").unwrap(), 44);
+ assert_eq!(path_purpose("M/49'/0'/0'/0/0").unwrap(), 49);
+ assert_eq!(path_purpose("86'/0'/0'/0/0").unwrap(), 86);
+ assert_eq!(path_purpose("m/84h/0h/0h/0/0").unwrap(), 84);
+ assert!(path_purpose("m/x/0").is_err());
+ }
+
+ #[test]
+ fn test_validate_cached_xpub_path_accepts_cached_mainnet_paths() {
+ for path in [
+ "44'/0'/0'/0/0",
+ "m/49'/0'/0'/0/0",
+ "M/84'/0'/0'/0/0",
+ "86'/0'/0'/0/0",
+ ] {
+ validate_cached_xpub_path("bitcoin-mainnet", path).unwrap();
+ }
+ }
+
+ #[test]
+ fn test_validate_cached_xpub_path_accepts_cached_paths_for_supported_networks() {
+ for network in ["bitcoin-testnet", "bitcoin-signet"] {
+ validate_cached_xpub_path(network, "m/84'/0'/0'/0/0").unwrap();
+ }
+ }
+
+ #[test]
+ fn test_validate_cached_xpub_path_rejects_non_cached_paths() {
+ assert!(validate_cached_xpub_path("bitcoin-mainnet", "m/44'/1'/0'/0/0").is_err());
+ assert!(validate_cached_xpub_path("bitcoin-mainnet", "m/48'/0'/0'/0/0").is_err());
+ assert!(validate_cached_xpub_path("bitcoin-mainnet", "m/44'/0'/1'/0/0").is_err());
+ assert!(validate_cached_xpub_path("bitcoin-mainnet", "m/44'/0'/0'").is_err());
+ assert!(validate_cached_xpub_path("bitcoin-mainnet", "m/84'/0'/0'/").is_err());
+ assert!(validate_cached_xpub_path("bitcoin-mainnet", "m/84'/0'/0'/0'").is_err());
+ assert!(validate_cached_xpub_path("bitcoin-mainnet", "m/84'/0'/0'/0'/0").is_err());
+ assert!(validate_cached_xpub_path("bitcoin-mainnet", "m/84'/0'/0'/0/0'").is_err());
+ assert!(validate_cached_xpub_path("bitcoin-mainnet", "m/84'/0'/0'/0/0/1").is_err());
+ assert!(validate_cached_xpub_path("bitcoin-mainnet", "m/84'/0'/0'/0/2147483648").is_err());
+ assert!(validate_cached_xpub_path(
+ "bitcoin-mainnet",
+ "m/44'/0'/0'/0/000000000000000000000000000000000000000000000000000"
+ )
+ .is_err());
+ assert!(validate_cached_xpub_path("bitcoin-regtest", "m/84'/0'/0'/0/0").is_err());
+ }
+
+ #[test]
+ fn test_verify_displayed_address_matches_seed_path() {
+ let seed = hex::decode(TEST_SEED_HEX).unwrap();
+ let key_path = "m/84'/0'/0'/0/0";
+ let address =
+ derive_connected_address_from_seed(&seed, "bitcoin-testnet", key_path).unwrap();
+ verify_displayed_address(&seed, "bitcoin-testnet", key_path, &address).unwrap();
+ }
+
+ #[test]
+ fn test_verify_displayed_address_rejects_mismatch() {
+ let seed = hex::decode(TEST_SEED_HEX).unwrap();
+ let err =
+ verify_displayed_address(&seed, "bitcoin-mainnet", "m/84'/0'/0'/0/0", "bc1qmismatch")
+ .unwrap_err();
+ assert!(matches!(err, RustCError::InvalidData(_)));
+ }
+
+ // `qr-hardware-call` CBOR produced by the JS lib (@keystonehq/bc-ur-registry) and
+ // pinned identically in the ur-registry Rust tests. Exercises the real decode path.
+ const JS_CBOR_HEX: &str = "a4010102d90517a40171626162796c6f6e2d6274632d7661756c74026f626974636f696e2d6d61696e6e657403d90130a1018a182cf500f500f500f400f4046864656164626565660367626162796c6f6e0401";
+
+ #[test]
+ fn test_decode_request_from_js_cbor() {
+ let bytes = hex::decode(JS_CBOR_HEX).unwrap();
+ let mut call = QRHardwareCall::try_from(bytes).unwrap();
+ let req = unsafe { decode_request(&mut call as *mut QRHardwareCall as PtrUR) }.unwrap();
+
+ assert_eq!(req.app_name, "babylon-btc-vault");
+ assert_eq!(req.network, "bitcoin-mainnet");
+ assert_eq!(req.key_path, "44'/0'/0'/0/0");
+ assert_eq!(req.context, "deadbeef");
+ assert_eq!(req.origin, Some("babylon".to_string()));
+ }
+
+ #[test]
+ fn test_compute_from_js_cbor_end_to_end() {
+ let bytes = hex::decode(JS_CBOR_HEX).unwrap();
+ let mut call = QRHardwareCall::try_from(bytes).unwrap();
+ let req = unsafe { decode_request(&mut call as *mut QRHardwareCall as PtrUR) }.unwrap();
+
+ let seed = hex::decode(TEST_SEED_HEX).unwrap();
+ let hash = compute_context_hash_hex(
+ &seed,
+ &req.app_name,
+ &req.network,
+ &req.key_path,
+ &req.context,
+ )
+ .unwrap();
+ // Deterministic result for (babylon-btc-vault, bitcoin-mainnet, 44'/0'/0'/0/0,
+ // deadbeef) over the standard abandon..about seed.
+ assert_eq!(
+ hash,
+ "564906234635460f327d8c7d87a5a49054b672b725d5b8f0bc4a239956f32ba2"
+ );
+ }
+
+ #[test]
+ fn test_context_hash_bytes_payload_uses_raw_hash_bytes() {
+ let hash_hex = "564906234635460f327d8c7d87a5a49054b672b725d5b8f0bc4a239956f32ba2";
+ let hash_bytes = hex::decode(hash_hex).unwrap();
+ let cbor: alloc::vec::Vec<u8> = Bytes::new(hash_bytes.clone()).try_into().unwrap();
+
+ assert_eq!(hash_bytes.len(), 32);
+ assert_eq!(cbor.len(), 34);
+ assert_eq!(hex::encode(&cbor[..2]), "5820");
+ assert_eq!(Bytes::try_from(cbor).unwrap().get_bytes(), hash_bytes);
+ }
+}
diff --git a/rust/rust_c/src/wallet/mod.rs b/rust/rust_c/src/wallet/mod.rs
index 545864c..0190618 100644
--- a/rust/rust_c/src/wallet/mod.rs
+++ b/rust/rust_c/src/wallet/mod.rs
@@ -1,5 +1,7 @@
pub mod btc_only_wallet;
pub use btc_only_wallet::*;
+#[cfg(feature = "multi-coins")]
+pub mod babylon;
#[cfg(feature = "cypherpunk")]
pub mod cypherpunk_wallet;
#[cfg(feature = "multi-coins")]
diff --git a/rust/rust_c/src/wallet/structs.rs b/rust/rust_c/src/wallet/structs.rs
index c1ac5c4..e0ffde6 100644
--- a/rust/rust_c/src/wallet/structs.rs
+++ b/rust/rust_c/src/wallet/structs.rs
@@ -53,6 +53,11 @@ impl TryFrom<&mut QRHardwareCall> for QRHardwareCallData {
Ok(Self {
call_type: convert_c_char(match value.get_call_type() {
CallType::KeyDerivation => "key_derivation".to_string(),
+ CallType::DeriveContextHash => {
+ return Err(RustCError::InvalidData(
+ "unexpected derive-context-hash params".to_string(),
+ ))
+ }
}),
origin: convert_c_char(value.get_origin().unwrap_or("unknown".to_string())),
key_derivation: KeyDerivationRequestData {
@@ -63,6 +68,10 @@ impl TryFrom<&mut QRHardwareCall> for QRHardwareCallData {
version: convert_c_char(value.get_version().to_string()),
})
}
+ // derive-context-hash uses its own parser (wallet::babylon); reject here.
+ CallParams::DeriveContextHash(_) => Err(RustCError::InvalidData(
+ "use parse_derive_context_hash for derive-context-hash calls".to_string(),
+ )),
}
}
}
diff --git a/src/config/version.h b/src/config/version.h
index 023eee9..4458ec5 100644
--- a/src/config/version.h
+++ b/src/config/version.h
@@ -7,7 +7,7 @@
#define SOFTWARE_VERSION_MAJOR 12
#define SOFTWARE_VERSION_MAJOR_OFFSET 10
#define SOFTWARE_VERSION_MINOR 4
-#define SOFTWARE_VERSION_BUILD 4
+#define SOFTWARE_VERSION_BUILD 6
#define SOFTWARE_VERSION_BETA 1
#define SOFTWARE_VERSION (SOFTWARE_VERSION_MAJOR * 10000 + SOFTWARE_VERSION_MINOR * 100 + SOFTWARE_VERSION_BUILD)
#ifdef WEB3_VERSION
diff --git a/src/ui/gui_analyze/gui_resolve_ur.c b/src/ui/gui_analyze/gui_resolve_ur.c
index 4c2c4e7..c8aa5e3 100644
--- a/src/ui/gui_analyze/gui_resolve_ur.c
+++ b/src/ui/gui_analyze/gui_resolve_ur.c
@@ -17,6 +17,7 @@
#ifdef WEB3_VERSION
#include "gui_key_derivation_request_widgets.h"
+#include "gui_derive_context_hash_request_widgets.h"
#include "gui_eth_batch_tx_widgets.h"
#endif
@@ -84,6 +85,9 @@ void handleURResult(URParseResult *urResult, URParseMultiResult *urMultiResult,
case KeyDerivationRequest:
GuiSetKeyDerivationRequestData(urResult, urMultiResult, is_multi);
break;
+ case DeriveContextHashRequest:
+ GuiSetDeriveContextHashRequestData(urResult, urMultiResult, is_multi);
+ break;
case EthBatchTx:
GuiSetEthBatchTxData(urResult, urMultiResult, is_multi);
break;
@@ -105,6 +109,7 @@ void handleURResult(URParseResult *urResult, URParseMultiResult *urMultiResult,
if (urViewType.viewType == WebAuthResult
#ifdef WEB3_VERSION
|| urViewType.viewType == KeyDerivationRequest
+ || urViewType.viewType == DeriveContextHashRequest
|| urViewType.viewType == EthBatchTx
#endif
#ifdef BTC_ONLY
diff --git a/src/ui/gui_assets/coin/coinBtcTestnet.c b/src/ui/gui_assets/coin/coinBtcTestnet.c
new file mode 100644
index 0000000..42f68f2
--- /dev/null
+++ b/src/ui/gui_assets/coin/coinBtcTestnet.c
@@ -0,0 +1,84 @@
+#if defined(LV_LVGL_H_INCLUDE_SIMPLE)
+#include "lvgl.h"
+#else
+#include "../lvgl/lvgl.h"
+#endif
+
+#ifndef LV_ATTRIBUTE_MEM_ALIGN
+#define LV_ATTRIBUTE_MEM_ALIGN
+#endif
+
+#ifndef LV_ATTRIBUTE_IMG_COINBTCTESTNET
+#define LV_ATTRIBUTE_IMG_COINBTCTESTNET
+#endif
+
+const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST LV_ATTRIBUTE_IMG_COINBTCTESTNET uint8_t coinBtcTestnet_map[] = {
+#if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP != 0
+ /*Pixel format: Blue: 5 bit Green: 6 bit, Red: 5 bit, Alpha 8 bit BUT the 2 color bytes are swapped*/
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x30, 0xB1, 0x55, 0x70, 0xB1, 0x55, 0x9F, 0xB1, 0x55, 0xCF, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0xBF, 0xB1, 0x55, 0x8F, 0xB1, 0x55, 0x70, 0xB1, 0x55, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x40, 0xB1, 0x55, 0x8F, 0xB1, 0x55, 0xEF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0x9F, 0xB1, 0x55, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x40, 0xB1, 0x55, 0xBF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xCF, 0xB1, 0x55, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x40, 0xB1, 0x55, 0xBF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x10, 0xB1, 0x55, 0x8F, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x8F, 0xB1, 0x55, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x20, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x30, 0xB1, 0x55, 0xEF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xEF, 0xB1, 0x55, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x5F, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x30, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x20, 0xB1, 0x55, 0xEF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xEF, 0xB1, 0x55, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x10, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDE, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x8F, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xDE, 0xFF, 0xFF, 0x18, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x20, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x5A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x5A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDE, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x18, 0xFF, 0xFF, 0x9C, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x18, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x18, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x60, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0xCF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, 0xFF, 0xFF, 0x9C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x30, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x9C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x9F, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x30, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x9C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x18, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x70, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x9C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x90, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x9C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x9F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0xBF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xDF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x9C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDE, 0xFF, 0xFF, 0x18, 0xFF, 0xFF, 0x5A, 0xFF, 0xFF, 0xDE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDE, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x9C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x18, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x9C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x9C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x18, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xDF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0xCF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0xA0, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x70, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x5A, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x9C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDE, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xDE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x30, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x18, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0xEF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xDF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x90, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x18, 0xFF, 0xFF, 0xDE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x9F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x40, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xDE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0xBF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xDE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDE, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x40, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x5A, 0xFF, 0xFF, 0x18, 0xFF, 0xFF, 0x5A, 0xFF, 0xFF, 0x9C, 0xFF, 0xFF, 0x9C, 0xFF, 0xFF, 0x5A, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0xBF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xDF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x40, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x90, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x18, 0xFF, 0xFF, 0x5A, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x9C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x10, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xFF, 0x18, 0xFF, 0xFF, 0xDE, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x20, 0xB1, 0x55, 0xEF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xEF, 0xB1, 0x55, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x30, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x60, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x30, 0xB1, 0x55, 0xEF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xEF, 0xB1, 0x55, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x20, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x10, 0xB1, 0x55, 0x90, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0x90, 0xB1, 0x55, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x20, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xBF, 0xB1, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x60, 0xB1, 0x55, 0xCF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xBF, 0xB1, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x30, 0xB1, 0x55, 0xA0, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xEF, 0xB1, 0x55, 0x90, 0xB1, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0x55, 0x30, 0xB1, 0x55, 0x70, 0xB1, 0x55, 0x90, 0xB1, 0x55, 0xBF, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xFF, 0xB1, 0x55, 0xDF, 0xB1, 0x55, 0xCF, 0xB1, 0x55, 0x9F, 0xB1, 0x55, 0x70, 0xB1, 0x55, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+#endif
+};
+
+const lv_img_dsc_t coinBtcTestnet = {
+ .header.always_zero = 0,
+ .header.w = 56,
+ .header.h = 56,
+ .data_size = 3136 * LV_IMG_PX_SIZE_ALPHA_BYTE,
+ .header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA,
+ .data = coinBtcTestnet_map,
+};
diff --git a/src/ui/gui_assets/images_hash.txt b/src/ui/gui_assets/images_hash.txt
index 797d5db..80e318a 100644
--- a/src/ui/gui_assets/images_hash.txt
+++ b/src/ui/gui_assets/images_hash.txt
@@ -1 +1 @@
-a59c564221b898f2e534c33680a099c2
\ No newline at end of file
+d92a67d0b034dbe745fb44204cf6818d
diff --git a/src/ui/gui_components/gui_attention_hintbox.c b/src/ui/gui_components/gui_attention_hintbox.c
index 46c8d41..5c69b9d 100644
--- a/src/ui/gui_components/gui_attention_hintbox.c
+++ b/src/ui/gui_components/gui_attention_hintbox.c
@@ -130,6 +130,11 @@ void GuiCloseAttentionHintbox()
}
void GuiCreateHardwareCallInvaildParamHintbox(char *title, char *content)
+{
+ GuiCreateHardwareCallInvaildParamHintboxWithHandler(title, content, CloseAttentionHandler);
+}
+
+void GuiCreateHardwareCallInvaildParamHintboxWithHandler(char *title, char *content, lv_event_cb_t okHandler)
{
AttentionHintboxContext *context = SRAM_MALLOC(sizeof(AttentionHintboxContext));
context->icon = &imgUnknown;
@@ -150,7 +155,7 @@ void GuiCreateHardwareCallInvaildParamHintbox(char *title, char *content)
lv_obj_set_style_radius(tempObj, 24, LV_PART_MAIN);
lv_obj_set_style_bg_color(tempObj, WHITE_COLOR_OPA20, LV_PART_MAIN);
lv_obj_align(tempObj, LV_ALIGN_BOTTOM_RIGHT, -36, -24);
- lv_obj_add_event_cb(tempObj, CloseAttentionHandler, LV_EVENT_CLICKED, NULL);
+ lv_obj_add_event_cb(tempObj, okHandler == NULL ? CloseAttentionHandler : okHandler, LV_EVENT_CLICKED, NULL);
SRAM_FREE(context);
}
@@ -253,4 +258,4 @@ void GuiCreateInitializatioCompleteHintbox()
lv_obj_add_event_cb(tempObj, CloseAttentionHandler, LV_EVENT_CLICKED, NULL);
SRAM_FREE(context);
-}
\ No newline at end of file
+}
diff --git a/src/ui/gui_components/gui_attention_hintbox.h b/src/ui/gui_components/gui_attention_hintbox.h
index 52d68dd..f9cf579 100644
--- a/src/ui/gui_components/gui_attention_hintbox.h
+++ b/src/ui/gui_components/gui_attention_hintbox.h
@@ -16,8 +16,9 @@ typedef enum {
void GuiCreateAttentionHintbox(uint16_t confirmSign);
void GuiCreateHardwareCallInvaildParamHintbox(char *title, char *context);
+void GuiCreateHardwareCallInvaildParamHintboxWithHandler(char *title, char *context, lv_event_cb_t okHandler);
void GuiCreateEnableBlindSigningHintbox();
void GuiCloseAttentionHintbox();
void GuiCreateInitializatioCompleteHintbox();
-#endif
\ No newline at end of file
+#endif
diff --git a/src/ui/gui_frame/gui_obj.h b/src/ui/gui_frame/gui_obj.h
index c43190a..6feb9c0 100644
--- a/src/ui/gui_frame/gui_obj.h
+++ b/src/ui/gui_frame/gui_obj.h
@@ -46,6 +46,7 @@ typedef int32_t(*GuiEventProcessFunc)(void *self, uint16_t usEvent, void *param,
add(SCREEN_CONNECTION) \
add(SCREEN_MULTI_ACCOUNTS_RECEIVE) \
add(SCREEN_KEY_DERIVATION_REQUEST) \
+ add(SCREEN_DERIVE_CONTEXT_HASH_REQUEST) \
add(SCREEN_SCAN) \
add(SCREEN_TRANSACTION_DETAIL) \
add(SCREEN_TRANSACTION_SIGNATURE) \
diff --git a/src/ui/gui_frame/gui_resource.h b/src/ui/gui_frame/gui_resource.h
index b2671b8..41f0b4f 100644
--- a/src/ui/gui_frame/gui_resource.h
+++ b/src/ui/gui_frame/gui_resource.h
@@ -177,6 +177,7 @@ LV_IMG_DECLARE(coinApt);
LV_IMG_DECLARE(coinAva);
LV_IMG_DECLARE(coinBnb);
LV_IMG_DECLARE(coinBtc);
+LV_IMG_DECLARE(coinBtcTestnet);
LV_IMG_DECLARE(coinAda);
LV_IMG_DECLARE(coinDot);
LV_IMG_DECLARE(coinEth);
diff --git a/src/ui/gui_model/gui_model.c b/src/ui/gui_model/gui_model.c
index 14a3d01..cc815df 100644
--- a/src/ui/gui_model/gui_model.c
+++ b/src/ui/gui_model/gui_model.c
@@ -698,8 +698,9 @@ static int32_t ModelURGenerateQRCode(const void *indata, uint32_t inDataLen, Bac
// printf("%s\r\n", g_urResult->data);
GuiApiEmitSignal(SIG_BACKGROUND_UR_GENERATE_SUCCESS, g_urResult->data, strnlen_s(g_urResult->data, SIMPLERESPONSE_C_CHAR_MAX_LEN) + 1);
} else {
- printf("error message: %s\r\n", g_urResult->error_message);
- //TODO: deal with error
+ char *message = g_urResult->error_message != NULL ? g_urResult->error_message : "";
+ printf("error message: %s\r\n", message);
+ GuiApiEmitSignal(SIG_BACKGROUND_UR_GENERATE_FAIL, message, strnlen_s(message, SIMPLERESPONSE_C_CHAR_MAX_LEN) + 1);
}
return SUCCESS_CODE;
}
diff --git a/src/ui/gui_views/gui_views.h b/src/ui/gui_views/gui_views.h
index 83e240e..39fa976 100644
--- a/src/ui/gui_views/gui_views.h
+++ b/src/ui/gui_views/gui_views.h
@@ -141,6 +141,7 @@ typedef enum {
SIG_WEB_AUTH_CODE_SUCCESS = SIG_FORGET_PASSWORD_BUTT + 50,
SIG_BACKGROUND_UR_GENERATE_SUCCESS,
+ SIG_BACKGROUND_UR_GENERATE_FAIL,
SIG_BACKGROUND_UR_UPDATE,
SIG_BACKGROUND_UR_BUTT,
@@ -220,6 +221,7 @@ extern GUI_VIEW g_DevicePublicKeyView;
#ifndef BTC_ONLY
extern GUI_VIEW g_multiAccountsReceiveView;
extern GUI_VIEW g_keyDerivationRequestView;
+extern GUI_VIEW g_deriveContextHashRequestView;
extern GUI_VIEW g_ethBatchTxView;
#endif
extern GUI_VIEW g_checkDeleteWalletView;
diff --git a/src/ui/gui_views/multi/gui_derive_context_hash_request_view.c b/src/ui/gui_views/multi/gui_derive_context_hash_request_view.c
new file mode 100644
index 0000000..ceed939
--- /dev/null
+++ b/src/ui/gui_views/multi/gui_derive_context_hash_request_view.c
@@ -0,0 +1,70 @@
+#include "gui.h"
+#include "gui_obj.h"
+#include "gui_views.h"
+#include "gui_derive_context_hash_request_widgets.h"
+#include "gui_keyboard_hintbox.h"
+#include "gui_lock_widgets.h"
+
+int32_t GuiDeriveContextHashRequestViewEventProcess(void *self, uint16_t usEvent, void *param, uint16_t usLen)
+{
+ bool isUsb = false;
+ switch (usEvent) {
+ case GUI_EVENT_OBJ_INIT:
+ if (param != NULL) {
+ isUsb = true;
+ }
+ GuiDeriveContextHashRequestInit(isUsb);
+ break;
+ case GUI_EVENT_OBJ_DEINIT:
+ GuiDeriveContextHashRequestDeInit();
+ break;
+ case GUI_EVENT_REFRESH:
+ GuiDeriveContextHashRequestRefresh();
+ break;
+ case SIG_BACKGROUND_UR_GENERATE_SUCCESS:
+ GuiDeriveContextHashWidgetHandleURGenerate((char *)param, usLen);
+ break;
+ case SIG_BACKGROUND_UR_UPDATE:
+ GuiDeriveContextHashWidgetHandleURUpdate((char *)param, usLen);
+ break;
+ case SIG_BACKGROUND_UR_GENERATE_FAIL:
+ GuiDeriveContextHashWidgetHandleURGenerateFail((char *)param);
+ break;
+ case SIG_VERIFY_PASSWORD_PASS:
+ if (param != NULL) {
+ uint16_t sig = *(uint16_t *)param;
+ if (sig == SIG_LOCK_VIEW_SCREEN_GO_HOME_PASS) {
+ GuiLockScreenToHome();
+ return SUCCESS_CODE;
+ }
+ }
+ DeriveContextHashHiddenKeyboardAndShowAnimateQR();
+ break;
+ case SIG_VERIFY_PASSWORD_FAIL:
+ if (param != NULL) {
+ PasswordVerifyResult_t *passwordVerifyResult = (PasswordVerifyResult_t *)param;
+ uint16_t sig = *(uint16_t *) passwordVerifyResult->signal;
+ if (sig == SIG_LOCK_VIEW_SCREEN_GO_HOME_PASS) {
+ GuiLockScreenPassCode(false);
+ GuiLockScreenErrorCount(param);
+ return SUCCESS_CODE;
+ }
+ }
+ GuiDeriveContextHashPasswordErrorCount(param);
+ break;
+ case SIG_INIT_PULLOUT_USB:
+ GuiDeriveContextHashUsbPullout();
+ break;
+ default:
+ return ERR_GUI_UNHANDLED;
+ }
+ return SUCCESS_CODE;
+}
+
+GUI_VIEW g_deriveContextHashRequestView = {
+ .id = SCREEN_DERIVE_CONTEXT_HASH_REQUEST,
+ .previous = NULL,
+ .isActive = false,
+ .optimization = false,
+ .pEvtHandler = GuiDeriveContextHashRequestViewEventProcess,
+};
diff --git a/src/ui/gui_widgets/gui_scan_widgets.c b/src/ui/gui_widgets/gui_scan_widgets.c
index 90b03f0..642df9b 100644
--- a/src/ui/gui_widgets/gui_scan_widgets.c
+++ b/src/ui/gui_widgets/gui_scan_widgets.c
@@ -134,6 +134,12 @@ void GuiScanResult(bool result, void *param)
}
GuiFrameOpenViewWithParam(&g_keyDerivationRequestView, NULL, 0);
}
+ if (g_qrcodeViewType == DeriveContextHashRequest) {
+ if (!GuiCheckIfTopView(&g_homeView)) {
+ GuiCloseCurrentWorkingView();
+ }
+ GuiFrameOpenViewWithParam(&g_deriveContextHashRequestView, NULL, 0);
+ }
#endif
#ifdef BTC_ONLY
diff --git a/src/ui/gui_widgets/gui_utxo_receive_widgets.c b/src/ui/gui_widgets/gui_utxo_receive_widgets.c
index 85b3ee9..837f065 100644
--- a/src/ui/gui_widgets/gui_utxo_receive_widgets.c
+++ b/src/ui/gui_widgets/gui_utxo_receive_widgets.c
@@ -211,6 +211,15 @@ static HOME_WALLET_CARD_ENUM g_chainCard;
static PageWidget_t *g_pageWidget;
static lv_obj_t *g_egCont = NULL;
static lv_obj_t *g_addressLabel[2];
+#ifdef WEB3_VERSION
+// Page-local toggle: when on, BTC receive addresses are shown in testnet/signet
+// encoding (re-encoded from the cached mainnet xpub). Resets each time the page opens.
+static bool g_btcShowAsTestAddress = false;
+static bool g_btcPreviewShowAsTestAddress = false;
+static lv_obj_t *g_btcTestAddressSwitch = NULL;
+static lv_obj_t *g_testnetWarningLabel = NULL;
+static lv_obj_t *g_fullscreenTestnetWarningLabel = NULL;
+#endif
static void InitDerivationPathDesc(uint8_t chain)
{
@@ -254,6 +263,10 @@ void GuiReceiveInit(uint8_t chain)
InitDerivationPathDesc(chain);
InitMultisigWalletConfig();
g_chainCard = chain;
+#ifdef WEB3_VERSION
+ g_btcShowAsTestAddress = false;
+ g_btcPreviewShowAsTestAddress = false;
+#endif
g_currentAccountIndex = GetCurrentAccountIndex();
g_selectIndex = GetCurrentSelectIndex();
#ifdef BTC_ONLY
@@ -308,6 +321,11 @@ void GuiReceiveDeInit(void)
DestroyPageWidget(g_pageWidget);
g_pageWidget = NULL;
}
+#ifdef WEB3_VERSION
+ g_btcTestAddressSwitch = NULL;
+ g_testnetWarningLabel = NULL;
+ g_fullscreenTestnetWarningLabel = NULL;
+#endif
#ifdef BTC_ONLY
if (g_multiSigWallet != NULL) {
free_MultiSigWallet(g_multiSigWallet);
@@ -332,6 +350,42 @@ static bool HasMoreBtn()
}
}
+#ifdef WEB3_VERSION
+static bool IsBtcTestAddressPreviewChanged(void)
+{
+ return g_chainCard == HOME_WALLET_CARD_BTC &&
+ g_btcPreviewShowAsTestAddress != g_btcShowAsTestAddress;
+}
+
+static bool IsBtcTestAddressEnabled(void)
+{
+ if (g_chainCard != HOME_WALLET_CARD_BTC) {
+ return false;
+ }
+ return g_utxoReceiveTileNow == UTXO_RECEIVE_TILE_ADDRESS_SETTINGS ?
+ g_btcPreviewShowAsTestAddress : g_btcShowAsTestAddress;
+}
+
+static void SetTestnetWarningVisible(lv_obj_t *label, bool visible)
+{
+ if (label == NULL) {
+ return;
+ }
+ if (visible) {
+ lv_obj_clear_flag(label, LV_OBJ_FLAG_HIDDEN);
+ } else {
+ lv_obj_add_flag(label, LV_OBJ_FLAG_HIDDEN);
+ }
+}
+
+static void UpdateTestnetWarnings(void)
+{
+ bool isTestAddress = IsBtcTestAddressEnabled();
+ SetTestnetWarningVisible(g_testnetWarningLabel, isTestAddress);
+ SetTestnetWarningVisible(g_fullscreenTestnetWarningLabel, isTestAddress);
+}
+#endif
+
void GuiReceiveRefresh(void)
{
switch (g_utxoReceiveTileNow) {
@@ -342,6 +396,11 @@ void GuiReceiveRefresh(void)
SetCoinWallet(g_pageWidget->navBarWidget, titleItem.type, titleItem.title);
SetNavBarRightBtn(g_pageWidget->navBarWidget, HasMoreBtn() ? NVS_BAR_MORE_INFO : NVS_RIGHT_BUTTON_BUTT, MoreHandler, NULL);
RefreshQrCode();
+#ifdef WEB3_VERSION
+ if (g_chainCard == HOME_WALLET_CARD_BTC && g_btcShowAsTestAddress) {
+ GuiUpdateStatusCoinButton(g_pageWidget->navBarWidget->midBtn, titleItem.title, &coinBtcTestnet);
+ }
+#endif
if (g_selectIndex == GetMaxAccountIndex()) {
lv_obj_set_style_img_opa(g_utxoReceiveWidgets.changeImg, LV_OPA_60, LV_PART_MAIN);
lv_obj_set_style_text_opa(g_utxoReceiveWidgets.changeLabel, LV_OPA_60, LV_PART_MAIN);
@@ -349,6 +408,9 @@ void GuiReceiveRefresh(void)
lv_obj_set_style_img_opa(g_utxoReceiveWidgets.changeImg, LV_OPA_COVER, LV_PART_MAIN);
lv_obj_set_style_text_opa(g_utxoReceiveWidgets.changeLabel, LV_OPA_COVER, LV_PART_MAIN);
}
+#ifdef WEB3_VERSION
+ UpdateTestnetWarnings();
+#endif
break;
case UTXO_RECEIVE_TILE_SWITCH_ACCOUNT:
SetNavBarLeftBtn(g_pageWidget->navBarWidget, NVS_BAR_RETURN, ReturnHandler, NULL);
@@ -373,6 +435,18 @@ void GuiReceiveRefresh(void)
SetMidBtnLabel(g_pageWidget->navBarWidget, NVS_BAR_MID_LABEL, _("receive_btc_more_address_settings"));
SetNavBarRightBtn(g_pageWidget->navBarWidget, NVS_RIGHT_BUTTON_BUTT, NULL, NULL);
g_selectType = g_addressType[g_currentAccountIndex];
+#ifdef WEB3_VERSION
+ if (g_chainCard == HOME_WALLET_CARD_BTC) {
+ g_btcPreviewShowAsTestAddress = g_btcShowAsTestAddress;
+ if (g_btcTestAddressSwitch != NULL) {
+ if (g_btcPreviewShowAsTestAddress) {
+ lv_obj_add_state(g_btcTestAddressSwitch, LV_STATE_CHECKED);
+ } else {
+ lv_obj_clear_state(g_btcTestAddressSwitch, LV_STATE_CHECKED);
+ }
+ }
+ }
+#endif
for (uint32_t i = 0; i < g_addressSettingsNum; i++) {
UpdateAddrTypeCheckbox(i, g_selectType == i);
}
@@ -486,6 +560,14 @@ lv_obj_t* CreateUTXOReceiveQRCode(lv_obj_t* parent, uint16_t w, uint16_t h)
lv_obj_add_flag(qrcode, LV_OBJ_FLAG_CLICKABLE);
lv_obj_add_event_cb(qrcode, GuiFullscreenModeHandler, LV_EVENT_CLICKED, NULL);
lv_qrcode_update(qrcode, "", 0);
+#ifdef WEB3_VERSION
+ if (w == 420 && h == 420 && g_chainCard == HOME_WALLET_CARD_BTC) {
+ g_fullscreenTestnetWarningLabel = GuiCreateIllustrateLabel(parent, _("This is a testnet address"));
+ lv_obj_set_style_text_color(g_fullscreenTestnetWarningLabel, RED_COLOR, LV_PART_MAIN);
+ lv_obj_align(g_fullscreenTestnetWarningLabel, LV_ALIGN_TOP_MID, 0, 96);
+ SetTestnetWarningVisible(g_fullscreenTestnetWarningLabel, g_btcShowAsTestAddress);
+ }
+#endif
return qrcode;
}
@@ -501,6 +583,13 @@ static void GuiCreateQrCodeWidget(lv_obj_t *parent)
lv_obj_set_style_bg_color(g_utxoReceiveWidgets.qrCodeCont, DARK_BG_COLOR, LV_PART_MAIN);
lv_obj_set_style_radius(g_utxoReceiveWidgets.qrCodeCont, 24, LV_PART_MAIN);
+#ifdef WEB3_VERSION
+ g_testnetWarningLabel = GuiCreateIllustrateLabel(g_utxoReceiveWidgets.qrCodeCont, _("This is a testnet address"));
+ lv_obj_set_style_text_color(g_testnetWarningLabel, RED_COLOR, LV_PART_MAIN);
+ lv_obj_align(g_testnetWarningLabel, LV_ALIGN_TOP_MID, 0, 8);
+ lv_obj_add_flag(g_testnetWarningLabel, LV_OBJ_FLAG_HIDDEN);
+#endif
+
yOffset += 36;
g_utxoReceiveWidgets.qrCode = CreateUTXOReceiveQRCode(g_utxoReceiveWidgets.qrCodeCont, 336, 336);
GuiFullscreenModeInit(480, 800, WHITE_COLOR);
@@ -735,7 +824,11 @@ static bool IsAddrTypeSelectChanged()
static void UpdateConfirmAddrTypeBtn(void)
{
- if (IsAddrTypeSelectChanged()) {
+ bool isChanged = IsAddrTypeSelectChanged();
+#ifdef WEB3_VERSION
+ isChanged = isChanged || IsBtcTestAddressPreviewChanged();
+#endif
+ if (isChanged) {
lv_obj_set_style_bg_opa(g_utxoReceiveWidgets.confirmAddrTypeBtn, LV_OPA_COVER, LV_PART_MAIN);
lv_obj_set_style_text_opa(lv_obj_get_child(g_utxoReceiveWidgets.confirmAddrTypeBtn, 0), LV_OPA_COVER, LV_PART_MAIN);
} else {
@@ -757,20 +850,33 @@ static void ConfirmAddrIndexHandler(lv_event_t *e)
static void ConfirmAddrTypeHandler(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
+ bool isAddrTypeChanged = IsAddrTypeSelectChanged();
+#ifdef WEB3_VERSION
+ bool isBtcTestAddressChanged = IsBtcTestAddressPreviewChanged();
+#else
+ bool isBtcTestAddressChanged = false;
+#endif
- if (code == LV_EVENT_CLICKED && IsAddrTypeSelectChanged()) {
- g_addressType[g_currentAccountIndex] = g_selectType;
-#ifdef BTC_ONLY
- if (GetIsTestNet()) {
- SetAccountTestReceivePath(GetCoinCardByIndex(g_chainCard)->coin, g_selectType);
- } else {
- SetAccountReceivePath(GetCoinCardByIndex(g_chainCard)->coin, g_selectType);
+ if (code == LV_EVENT_CLICKED && (isAddrTypeChanged || isBtcTestAddressChanged)) {
+#ifdef WEB3_VERSION
+ if (g_chainCard == HOME_WALLET_CARD_BTC) {
+ g_btcShowAsTestAddress = g_btcPreviewShowAsTestAddress;
}
+#endif
+ if (isAddrTypeChanged) {
+ g_addressType[g_currentAccountIndex] = g_selectType;
+#ifdef BTC_ONLY
+ if (GetIsTestNet()) {
+ SetAccountTestReceivePath(GetCoinCardByIndex(g_chainCard)->coin, g_selectType);
+ } else {
+ SetAccountReceivePath(GetCoinCardByIndex(g_chainCard)->coin, g_selectType);
+ }
#else
- SetAccountReceivePath(GetCoinCardByIndex(g_chainCard)->coin, g_selectType);
+ SetAccountReceivePath(GetCoinCardByIndex(g_chainCard)->coin, g_selectType);
#endif
- g_selectIndex = 0;
- SetCurrentSelectIndex(g_selectIndex);
+ g_selectIndex = 0;
+ SetCurrentSelectIndex(g_selectIndex);
+ }
ReturnHandler(e);
}
}
@@ -900,7 +1006,31 @@ static void ShowEgAddressCont(lv_obj_t *egCont)
lv_obj_t *prevLabel, *label;
int egContHeight = 80 + 12;
#ifndef BTC_ONLY
- label = GuiCreateNoticeLabel(egCont, g_derivationPathDescs[g_selectType]);
+#ifdef WEB3_VERSION
+ if (g_chainCard == HOME_WALLET_CARD_BTC && g_btcPreviewShowAsTestAddress) {
+ switch (g_selectType) {
+ case BTC_NATIVE_SEGWIT:
+ label = GuiCreateNoticeLabel(egCont, _("derivation_path_btc_test_net_1_desc"));
+ break;
+ case BTC_TAPROOT:
+ label = GuiCreateNoticeLabel(egCont, _("derivation_path_btc_test_net_4_desc"));
+ break;
+ case BTC_NESTED_SEGWIT:
+ label = GuiCreateNoticeLabel(egCont, _("derivation_path_btc_test_net_2_desc"));
+ break;
+ case BTC_LEGACY:
+ label = GuiCreateNoticeLabel(egCont, _("derivation_path_btc_test_net_3_desc"));
+ break;
+ default:
+ label = GuiCreateNoticeLabel(egCont, g_derivationPathDescs[g_selectType]);
+ break;
+ }
+ } else {
+#endif
+ label = GuiCreateNoticeLabel(egCont, g_derivationPathDescs[g_selectType]);
+#ifdef WEB3_VERSION
+ }
+#endif
#else
if (GetIsTestNet()) {
label = GuiCreateNoticeLabel(egCont, g_testNetderivationPathDescs[g_selectType]);
@@ -965,6 +1095,16 @@ static void GetChangePathLabelHint(char* hint, uint32_t maxLen)
}
}
+#ifdef WEB3_VERSION
+static void BtcTestnetSwitchHandler(lv_event_t *e)
+{
+ lv_obj_t *sw = lv_event_get_target(e);
+ g_btcPreviewShowAsTestAddress = lv_obj_has_state(sw, LV_STATE_CHECKED);
+ ShowEgAddressCont(g_egCont);
+ UpdateConfirmAddrTypeBtn();
+}
+#endif
+
static void GuiCreateAddressSettingsWidget(lv_obj_t *parent)
{
if (g_derivationPathDescs == NULL) {
@@ -1034,8 +1174,34 @@ static void GuiCreateAddressSettingsWidget(lv_obj_t *parent)
lv_obj_clear_flag(g_utxoReceiveWidgets.addressSettingsWidgets[g_addressType[g_currentAccountIndex]].checkedImg, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(g_utxoReceiveWidgets.addressSettingsWidgets[g_addressType[g_currentAccountIndex]].uncheckedImg, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_t *egAnchor = cont;
+#ifdef WEB3_VERSION
+ if (g_chainCard == HOME_WALLET_CARD_BTC) {
+ lv_obj_t *toggleCont = GuiCreateContainerWithParent(scrollCont, 408, 84);
+ lv_obj_align_to(toggleCont, cont, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 24);
+ lv_obj_set_style_bg_color(toggleCont, WHITE_COLOR, LV_PART_MAIN);
+ lv_obj_set_style_bg_opa(toggleCont, LV_OPA_10 + LV_OPA_2, LV_PART_MAIN);
+ lv_obj_set_style_radius(toggleCont, 24, LV_PART_MAIN);
+
+ lv_obj_t *toggleLabel = GuiCreateTextLabel(toggleCont, _("Show as Testnet / Signet"));
+ lv_obj_align(toggleLabel, LV_ALIGN_LEFT_MID, 24, 0);
+
+ lv_obj_t *testnetSwitch = GuiCreateSwitch(toggleCont);
+ g_btcTestAddressSwitch = testnetSwitch;
+ lv_obj_align(testnetSwitch, LV_ALIGN_RIGHT_MID, -24, 0);
+ if (g_btcPreviewShowAsTestAddress) {
+ lv_obj_add_state(testnetSwitch, LV_STATE_CHECKED);
+ } else {
+ lv_obj_clear_state(testnetSwitch, LV_STATE_CHECKED);
+ }
+ lv_obj_add_event_cb(testnetSwitch, BtcTestnetSwitchHandler, LV_EVENT_VALUE_CHANGED, NULL);
+
+ egAnchor = toggleCont;
+ }
+#endif
+
lv_obj_t *egCont = GuiCreateContainerWithParent(scrollCont, 408, 186);
- lv_obj_align_to(egCont, cont, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 24);
+ lv_obj_align_to(egCont, egAnchor, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 24);
lv_obj_set_style_bg_color(egCont, WHITE_COLOR, LV_PART_MAIN);
lv_obj_set_style_bg_opa(egCont, LV_OPA_10 + LV_OPA_2, LV_PART_MAIN);
lv_obj_set_style_radius(egCont, 24, LV_PART_MAIN);
@@ -1575,7 +1741,14 @@ static void ModelGetUtxoAddress(uint32_t index, AddressDataItem_t *item)
ASSERT(xPub);
SimpleResponse_c_char *result;
do {
- result = utxo_get_address(hdPath, xPub);
+#ifdef WEB3_VERSION
+ if (IsBtcTestAddressEnabled()) {
+ result = btcoin_get_address_with_network(hdPath, xPub, "bitcoin-testnet");
+ } else
+#endif
+ {
+ result = utxo_get_address(hdPath, xPub);
+ }
CHECK_CHAIN_BREAK(result);
} while (0);
strcpy_s(item->address, ADDRESS_MAX_LEN, result->data);
diff --git a/src/ui/gui_widgets/multi/gui_derive_context_hash_request_widgets.c b/src/ui/gui_widgets/multi/gui_derive_context_hash_request_widgets.c
new file mode 100644
index 0000000..88d8d63
--- /dev/null
+++ b/src/ui/gui_widgets/multi/gui_derive_context_hash_request_widgets.c
@@ -0,0 +1,398 @@
+#include "gui.h"
+#include "gui_page.h"
+#include "librust_c.h"
+#include "keystore.h"
+#include "user_memory.h"
+#include "gui_button.h"
+#include "gui_hintbox.h"
+#include "gui_obj.h"
+#include "gui_views.h"
+#include "gui_chain.h"
+#include "secret_cache.h"
+#include "account_manager.h"
+#include "account_public_info.h"
+#include "gui_keyboard_hintbox.h"
+#include "gui_lock_widgets.h"
+#include "gui_animating_qrcode.h"
+#include "gui_attention_hintbox.h"
+#include "gui_pending_hintbox.h"
+#include "gui_derive_context_hash_request_widgets.h"
+
+typedef struct {
+ uint8_t currentTile;
+ PageWidget_t *pageWidget;
+ lv_obj_t *tileView;
+ lv_obj_t *qrCode;
+} DeriveContextHashWidget_t;
+
+typedef enum {
+ TILE_APPROVE,
+ TILE_QRCODE,
+
+ TILE_BUTT,
+} PAGE_TILE;
+
+// Slider value (0-100) at which the confirm action triggers, matching the sign-tx UI.
+#define DCH_CONFIRM_SLIDE_PROCESS 66
+
+static URParseResult *g_urResult = NULL;
+static URParseMultiResult *g_urMultiResult = NULL;
+static bool g_isMulti = false;
+static void *g_data = NULL;
+static DeriveContextHashWidget_t g_widget;
+static DeriveContextHashCallData *g_callData = NULL;
+static Response_DeriveContextHashCallData *g_response = NULL;
+static char *g_address = NULL;
+static lv_obj_t *g_approveTile = NULL;
+static KeyboardWidget_t *g_keyboardWidget = NULL;
+
+static void FreeDeriveContextHashMemory(void);
+static void ModelParse(void);
+static bool DeriveConnectedAddress(void);
+static UREncodeResult *ModelGenerateSyncUR(void);
+static void GuiCreateApproveWidget(lv_obj_t *parent);
+static void GuiCreateQRCodeWidget(lv_obj_t *parent);
+static void ConfirmSliderHandler(lv_event_t *e);
+static void CloseInvalidRequestHandler(lv_event_t *e);
+static void OnReturnHandler(lv_event_t *e);
+static void NormalizeDisplayKeyPath(char *out, uint32_t outSize, const char *keyPath);
+static void GuiShowKeyBoardDialog(lv_obj_t *parent);
+static void NextTile(void);
+static void PrevTile(void);
+
+void GuiSetDeriveContextHashRequestData(void *urResult, void *multiResult, bool is_multi)
+{
+ FreeDeriveContextHashMemory();
+ g_urResult = urResult;
+ g_urMultiResult = multiResult;
+ g_isMulti = is_multi;
+ g_data = g_isMulti ? g_urMultiResult->data : g_urResult->data;
+}
+
+static void FreeDeriveContextHashMemory(void)
+{
+ CHECK_FREE_UR_RESULT(g_urResult, false);
+ CHECK_FREE_UR_RESULT(g_urMultiResult, true);
+ if (g_response != NULL) {
+ free_Response_DeriveContextHashCallData(g_response);
+ g_response = NULL;
+ }
+ if (g_address != NULL) {
+ SRAM_FREE(g_address);
+ g_address = NULL;
+ }
+}
+
+static void ModelParse(void)
+{
+ g_response = parse_derive_context_hash(g_data);
+ g_callData = g_response->data;
+}
+
+// Derive the connected key's address (seedless, from the cached account xpub) so the
+// user can confirm it on the approval screen.
+static bool DeriveConnectedAddress(void)
+{
+ if (g_callData == NULL || g_callData->key_path == NULL) {
+ return false;
+ }
+
+ const char *keyPath = g_callData->key_path;
+ if (strncmp(keyPath, "m/", 2) == 0 || strncmp(keyPath, "M/", 2) == 0) {
+ keyPath += 2;
+ }
+ int purpose = atoi(keyPath);
+ ChainType xpubType;
+ switch (purpose) {
+ case 44:
+ xpubType = XPUB_TYPE_BTC_LEGACY;
+ break;
+ case 49:
+ xpubType = XPUB_TYPE_BTC;
+ break;
+ case 84:
+ xpubType = XPUB_TYPE_BTC_NATIVE_SEGWIT;
+ break;
+ case 86:
+ xpubType = XPUB_TYPE_BTC_TAPROOT;
+ break;
+ default:
+ return false;
+ }
+ char *xpub = GetCurrentAccountPublicKey(xpubType);
+ if (xpub == NULL) {
+ return false;
+ }
+ char hdPath[BUFFER_SIZE_64] = {0};
+ NormalizeDisplayKeyPath(hdPath, sizeof(hdPath), g_callData->key_path);
+ if (hdPath[0] == '\0') {
+ return false;
+ }
+ SimpleResponse_c_char *result = btcoin_get_address_with_network(hdPath, xpub, g_callData->network);
+ if (result->error_code == 0 && result->data != NULL) {
+ g_address = SRAM_MALLOC(strnlen_s(result->data, BUFFER_SIZE_128) + 1);
+ strcpy(g_address, result->data);
+ free_simple_response_c_char(result);
+ return true;
+ }
+ free_simple_response_c_char(result);
+ return false;
+}
+
+void GuiDeriveContextHashRequestInit(bool isUsb)
+{
+ GUI_PAGE_DEL(g_widget.pageWidget);
+ g_widget.pageWidget = CreatePageWidget();
+ SetNavBarLeftBtn(g_widget.pageWidget->navBarWidget, NVS_BAR_RETURN, CloseCurrentViewHandler, NULL);
+ SetMidBtnLabel(g_widget.pageWidget->navBarWidget, NVS_BAR_MID_LABEL, _("Derive Context Hash"));
+
+ ModelParse();
+ // Rust validated the request (appName allow-list, network, context, key path).
+ // On failure `g_callData` is NULL, so show the error and skip the confirm UI
+ // instead of dereferencing it. The nav-bar back button returns to the scanner.
+ if (g_response->error_code != 0 || g_callData == NULL) {
+ char *message = g_response->error_message != NULL
+ ? g_response->error_message
+ : (char *)_("Invalid derive-context-hash request");
+ GuiCreateHardwareCallInvaildParamHintboxWithHandler(
+ (char *)_("Invalid Request"), message, CloseInvalidRequestHandler);
+ return;
+ }
+ if (!DeriveConnectedAddress()) {
+ GuiCreateHardwareCallInvaildParamHintboxWithHandler(
+ (char *)_("Invalid Request"), (char *)_("Invalid derive-context-hash key path"),
+ CloseInvalidRequestHandler);
+ return;
+ }
+
+ lv_obj_t *tileView = GuiCreateTileView(g_widget.pageWidget->contentZone);
+ lv_obj_t *tile = lv_tileview_add_tile(tileView, TILE_APPROVE, 0, LV_DIR_HOR);
+ GuiCreateApproveWidget(tile);
+
+ tile = lv_tileview_add_tile(tileView, TILE_QRCODE, 0, LV_DIR_HOR);
+ GuiCreateQRCodeWidget(tile);
+
+ g_widget.tileView = tileView;
+ g_widget.currentTile = TILE_APPROVE;
+ lv_obj_set_tile_id(g_widget.tileView, g_widget.currentTile, 0, LV_ANIM_OFF);
+}
+
+void GuiDeriveContextHashRequestDeInit(void)
+{
+ GuiDeleteKeyboardWidget(g_keyboardWidget);
+ GuiCloseAttentionHintbox();
+ GUI_PAGE_DEL(g_widget.pageWidget);
+ GuiAnimatingQRCodeDestroyTimer();
+ FreeDeriveContextHashMemory();
+ g_approveTile = NULL;
+ SetPageLockScreen(true);
+}
+
+void GuiDeriveContextHashRequestRefresh(void)
+{
+ GuiAnimatingQRCodeControl(false);
+}
+
+static void CreateInfoRow(lv_obj_t *parent, const char *title, const char *value)
+{
+ GuiCreateNoticeLabel(parent, title);
+ lv_obj_t *valueLabel = GuiCreateIllustrateLabel(parent, value == NULL ? "" : value);
+ lv_obj_set_width(valueLabel, lv_pct(100));
+ lv_label_set_long_mode(valueLabel, LV_LABEL_LONG_WRAP);
+}
+
+static void NormalizeDisplayKeyPath(char *out, uint32_t outSize, const char *keyPath)
+{
+ if (out == NULL || outSize == 0) {
+ return;
+ }
+ out[0] = '\0';
+ if (keyPath == NULL) {
+ return;
+ }
+ if (strncmp(keyPath, "m/", 2) == 0 || strncmp(keyPath, "M/", 2) == 0) {
+ snprintf_s(out, outSize, "%s", keyPath);
+ out[0] = 'M';
+ } else {
+ snprintf_s(out, outSize, "M/%s", keyPath);
+ }
+}
+
+static void GuiCreateApproveWidget(lv_obj_t *parent)
+{
+ g_approveTile = parent;
+ lv_obj_t *label = GuiCreateIllustrateLabel(parent, _("Derive Context Hash Request"));
+ lv_obj_align(label, LV_ALIGN_TOP_LEFT, 36, 8);
+
+ // Outer fixed-height card = the visible scroll window.
+ lv_obj_t *cont = GuiCreateContainerWithParent(parent, 408, 480);
+ lv_obj_align(cont, LV_ALIGN_TOP_MID, 0, 50);
+ lv_obj_set_style_radius(cont, 24, LV_PART_MAIN);
+ lv_obj_set_style_bg_color(cont, WHITE_COLOR, LV_PART_MAIN);
+ lv_obj_set_style_bg_opa(cont, LV_OPA_12, LV_PART_MAIN);
+ lv_obj_add_flag(cont, LV_OBJ_FLAG_SCROLLABLE);
+ lv_obj_add_flag(cont, LV_OBJ_FLAG_CLICKABLE);
+ lv_obj_set_scroll_dir(cont, LV_DIR_VER);
+ lv_obj_set_scrollbar_mode(cont, LV_SCROLLBAR_MODE_AUTO);
+
+ // Inner content grows with the fields (flex column, height = content). When it is
+ // taller than the card, the card scrolls vertically.
+ lv_obj_t *inner = lv_obj_create(cont);
+ lv_obj_set_width(inner, lv_pct(100));
+ lv_obj_set_height(inner, LV_SIZE_CONTENT);
+ lv_obj_set_style_bg_opa(inner, LV_OPA_0, LV_PART_MAIN);
+ lv_obj_set_style_border_width(inner, 0, LV_PART_MAIN);
+ lv_obj_set_style_radius(inner, 0, LV_PART_MAIN);
+ lv_obj_set_style_pad_all(inner, 24, LV_PART_MAIN);
+ lv_obj_set_style_pad_row(inner, 8, LV_PART_MAIN);
+ lv_obj_set_flex_flow(inner, LV_FLEX_FLOW_COLUMN);
+ lv_obj_set_flex_align(inner, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
+ lv_obj_clear_flag(inner, LV_OBJ_FLAG_SCROLLABLE);
+
+ char path[BUFFER_SIZE_64] = {0};
+ NormalizeDisplayKeyPath(path, sizeof(path), g_callData->key_path);
+ CreateInfoRow(inner, _("App Name"), g_callData->app_name);
+ CreateInfoRow(inner, _("From"), g_callData->origin);
+ CreateInfoRow(inner, _("Network"), g_callData->network);
+ CreateInfoRow(inner, _("Path"), path);
+ if (g_address != NULL) {
+ CreateInfoRow(inner, _("Address"), g_address);
+ }
+ CreateInfoRow(inner, _("Context"), g_callData->context);
+
+ // Reuse the sign-tx "slide to confirm" component for a consistent approval UX.
+ // (Reject is handled by the nav-bar back button.)
+ GuiCreateConfirmSlider(parent, ConfirmSliderHandler);
+}
+
+static void GuiCreateQRCodeWidget(lv_obj_t *parent)
+{
+ lv_obj_t *label = GuiCreateIllustrateLabel(parent, _("connect_wallet_scan"));
+ lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 152 - GUI_MAIN_AREA_OFFSET);
+ lv_obj_set_style_text_opa(label, LV_OPA_60, LV_PART_MAIN);
+
+ lv_obj_t *qrCont = GuiCreateContainerWithParent(parent, 408, 408);
+ lv_obj_add_flag(qrCont, LV_OBJ_FLAG_CLICKABLE);
+ lv_obj_align(qrCont, LV_ALIGN_TOP_MID, 0, 80);
+ lv_obj_set_style_bg_color(qrCont, DARK_BG_COLOR, LV_PART_MAIN);
+ lv_obj_set_style_radius(qrCont, 24, LV_PART_MAIN);
+
+ lv_obj_t *qrBgCont = GuiCreateContainerWithParent(qrCont, 336, 336);
+ lv_obj_align(qrBgCont, LV_ALIGN_TOP_MID, 0, 36);
+ lv_obj_set_style_bg_color(qrBgCont, WHITE_COLOR, LV_PART_MAIN);
+
+ lv_obj_t *qrcode = GuiCreateContainerWithParent(qrBgCont, 294, 294);
+ lv_obj_align(qrcode, LV_ALIGN_TOP_MID, 0, 21);
+ g_widget.qrCode = qrcode;
+
+ lv_obj_t *btn = GuiCreateTextBtn(parent, _("Done"));
+ lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, -24);
+ lv_obj_set_size(btn, 408, 66);
+ lv_obj_add_event_cb(btn, GoToHomeViewHandler, LV_EVENT_CLICKED, NULL);
+}
+
+static void GuiShowKeyBoardDialog(lv_obj_t *parent)
+{
+ g_keyboardWidget = GuiCreateKeyboardWidget(parent);
+ SetKeyboardWidgetSelf(g_keyboardWidget, &g_keyboardWidget);
+ static uint16_t sig = SIG_HARDWARE_CALL_DERIVE_PUBKEY;
+ SetKeyboardWidgetSig(g_keyboardWidget, &sig);
+}
+
+static void ConfirmSliderHandler(lv_event_t *e)
+{
+ if (lv_event_get_code(e) != LV_EVENT_RELEASED) {
+ return;
+ }
+ lv_obj_t *slider = lv_event_get_target(e);
+ if (lv_slider_get_value(slider) >= DCH_CONFIRM_SLIDE_PROCESS) {
+ lv_slider_set_value(slider, 0, LV_ANIM_OFF);
+ GuiShowKeyBoardDialog(g_approveTile);
+ } else {
+ lv_slider_set_value(slider, 0, LV_ANIM_ON);
+ }
+}
+
+static void CloseInvalidRequestHandler(lv_event_t *e)
+{
+ GuiCloseAttentionHintbox();
+ CloseCurrentViewHandler(e);
+}
+
+static void OnReturnHandler(lv_event_t *e)
+{
+ PrevTile();
+}
+
+static UREncodeResult *ModelGenerateSyncUR(void)
+{
+ bool enable = IsPreviousLockScreenEnable();
+ SetLockScreen(false);
+ uint8_t seed[64] = {0};
+ char *password = SecretCacheGetPassword();
+ MnemonicType mnemonicType = GetMnemonicType();
+ int seedLen = (mnemonicType == MNEMONIC_TYPE_SLIP39) ? GetCurrentAccountEntropyLen() : sizeof(seed);
+ GetAccountSeed(GetCurrentAccountIndex(), seed, password);
+ UREncodeResult *urResult = generate_derive_context_hash_ur(g_data, seed, seedLen, g_address);
+ memset(seed, 0, sizeof(seed));
+ ClearSecretCache();
+ SetLockScreen(enable);
+ return urResult;
+}
+
+void DeriveContextHashHiddenKeyboardAndShowAnimateQR(void)
+{
+ GuiDeleteKeyboardWidget(g_keyboardWidget);
+ GuiAnimatingQRCodeInit(g_widget.qrCode, ModelGenerateSyncUR, true);
+ NextTile();
+}
+
+void GuiDeriveContextHashWidgetHandleURGenerate(char *data, uint16_t len)
+{
+ GuiAnimantingQRCodeFirstUpdate(data, len);
+}
+
+void GuiDeriveContextHashWidgetHandleURGenerateFail(char *message)
+{
+ GuiPendingHintBoxRemove();
+ GuiAnimatingQRCodeDestroyTimer();
+ GuiCreateHardwareCallInvaildParamHintboxWithHandler(
+ (char *)_("Invalid Request"),
+ message != NULL ? message : (char *)_("Invalid derive-context-hash request"),
+ CloseInvalidRequestHandler);
+}
+
+void GuiDeriveContextHashWidgetHandleURUpdate(char *data, uint16_t len)
+{
+ GuiAnimatingQRCodeUpdate(data, len);
+}
+
+void GuiDeriveContextHashPasswordErrorCount(void *param)
+{
+ PasswordVerifyResult_t *passwordVerifyResult = (PasswordVerifyResult_t *)param;
+ GuiShowErrorNumber(g_keyboardWidget, passwordVerifyResult);
+}
+
+void GuiDeriveContextHashUsbPullout(void)
+{
+ // USB transport is not wired for derive-context-hash yet (QR only).
+}
+
+static void NextTile(void)
+{
+ g_widget.currentTile++;
+ if (g_widget.currentTile == TILE_QRCODE) {
+ SetNavBarLeftBtn(g_widget.pageWidget->navBarWidget, NVS_BAR_RETURN, OnReturnHandler, NULL);
+ }
+ lv_obj_set_tile_id(g_widget.tileView, g_widget.currentTile, 0, LV_ANIM_OFF);
+}
+
+static void PrevTile(void)
+{
+ g_widget.currentTile--;
+ if (g_widget.currentTile == TILE_APPROVE) {
+ SetNavBarLeftBtn(g_widget.pageWidget->navBarWidget, NVS_BAR_RETURN, CloseCurrentViewHandler, NULL);
+ GuiAnimatingQRCodeDestroyTimer();
+ }
+ lv_obj_set_tile_id(g_widget.tileView, g_widget.currentTile, 0, LV_ANIM_OFF);
+}
diff --git a/src/ui/gui_widgets/multi/gui_derive_context_hash_request_widgets.h b/src/ui/gui_widgets/multi/gui_derive_context_hash_request_widgets.h
new file mode 100644
index 0000000..57504a1
--- /dev/null
+++ b/src/ui/gui_widgets/multi/gui_derive_context_hash_request_widgets.h
@@ -0,0 +1,19 @@
+#ifndef _GUI_DERIVE_CONTEXT_HASH_REQUEST_WIDGETS_H_
+#define _GUI_DERIVE_CONTEXT_HASH_REQUEST_WIDGETS_H_
+
+#include "stdlib.h"
+#include "stdint.h"
+#include "stdbool.h"
+
+void GuiDeriveContextHashRequestInit(bool isUsb);
+void GuiDeriveContextHashRequestDeInit(void);
+void GuiDeriveContextHashRequestRefresh(void);
+void GuiDeriveContextHashWidgetHandleURGenerate(char *data, uint16_t len);
+void GuiDeriveContextHashWidgetHandleURGenerateFail(char *message);
+void GuiDeriveContextHashWidgetHandleURUpdate(char *data, uint16_t len);
+void GuiSetDeriveContextHashRequestData(void *urResult, void *multiResult, bool is_multi);
+void GuiDeriveContextHashPasswordErrorCount(void *param);
+void GuiDeriveContextHashUsbPullout(void);
+void DeriveContextHashHiddenKeyboardAndShowAnimateQR(void);
+
+#endif
Why this scored 37/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.