diff --git a/.github/workflows/build-release-ios.yml b/.github/workflows/build-release-ios.yml index 3ea24d03..0f87db6d 100644 --- a/.github/workflows/build-release-ios.yml +++ b/.github/workflows/build-release-ios.yml @@ -52,12 +52,12 @@ jobs: - name: Install Rust run: | - rustup toolchain install nightly-2022-10-31 - rustup default nightly-2022-10-31-x86_64-apple-darwin + rustup toolchain install nightly-2023-06-15 + rustup default nightly-2023-06-15-x86_64-apple-darwin rustup target add aarch64-apple-ios x86_64-apple-ios rustup show cargo install cargo-lipo - cargo install cbindgen + cargo install cbindgen --version 0.26.0 brew install protobuf - name: Read VERSION file diff --git a/Cargo.lock b/Cargo.lock index ad3526d7..b902b816 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -184,6 +184,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -246,6 +252,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "bitstream-io" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c12d1856e42f0d817a835fe55853957c85c8c8a470114029143d3f12671446e" + [[package]] name = "bitvec" version = "1.0.1" @@ -837,6 +849,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crunchy" version = "0.2.2" @@ -2602,6 +2629,7 @@ dependencies = [ "autocfg", "num-integer", "num-traits", + "serde", ] [[package]] @@ -4333,6 +4361,7 @@ dependencies = [ "tcx-proto", "tcx-substrate", "tcx-tezos", + "tcx-ton", "tcx-tron", ] @@ -4714,6 +4743,21 @@ dependencies = [ "tcx-primitive", ] +[[package]] +name = "tcx-ton" +version = "0.1.0" +dependencies = [ + "anyhow", + "prost", + "prost-types", + "tcx-common", + "tcx-constants", + "tcx-crypto", + "tcx-keystore", + "tcx-primitive", + "tonlib-core", +] + [[package]] name = "tcx-tron" version = "0.1.0" @@ -4953,6 +4997,24 @@ dependencies = [ "winnow", ] +[[package]] +name = "tonlib-core" +version = "0.21.1" +dependencies = [ + "base64 0.22.1", + "bitstream-io", + "crc", + "hex", + "hmac 0.12.1", + "lazy_static", + "num-bigint 0.4.3", + "num-traits", + "serde", + "serde_json", + "sha2 0.10.6", + "thiserror", +] + [[package]] name = "tower-service" version = "0.3.2" diff --git a/Cargo.toml b/Cargo.toml index 80fabb7d..dfc1439a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,11 @@ members = [ "token-core/tcx-tester", "token-core/tcx-eth2", "token-core/tcx-eth", + "token-core/tcx-ton", "token-core/tcx-common", "token-core/tcx-migration", "token-core/tcx-libs/ed25519-dalek-bip32", + "token-core/tcx-libs/tonlib-core", "imkey-core/ikc", "imkey-core/ikc-common", "imkey-core/ikc-device", diff --git a/token-core/tcx-atom/src/address.rs b/token-core/tcx-atom/src/address.rs index 32056425..0a7d62fa 100644 --- a/token-core/tcx-atom/src/address.rs +++ b/token-core/tcx-atom/src/address.rs @@ -89,6 +89,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), } } @@ -166,6 +167,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let address = AtomAddress::from_public_key(&pub_key, &coin_info) .unwrap() @@ -214,6 +216,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let address = AtomAddress::from_public_key(&pub_key, &coin_info) @@ -237,6 +240,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let address = AtomAddress::from_public_key(&pub_key, &coin_info); @@ -252,6 +256,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let address = AtomAddress::from_public_key(&pub_key, &coin_info); @@ -264,6 +269,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let address = AtomAddress::from_public_key(&pub_key, &coin_info); diff --git a/token-core/tcx-atom/src/signer.rs b/token-core/tcx-atom/src/signer.rs index 0ceaca7f..38f3bc69 100644 --- a/token-core/tcx-atom/src/signer.rs +++ b/token-core/tcx-atom/src/signer.rs @@ -3,9 +3,9 @@ use tcx_keystore::{ Keystore, Result, SignatureParameters, Signer, TransactionSigner as TraitTransactionSigner, }; -use tcx_common::{sha256, FromHex}; - +use anyhow::anyhow; use base64; +use tcx_common::{sha256, FromHex}; const SIG_LEN: usize = 64; @@ -15,6 +15,11 @@ impl TraitTransactionSigner for Keystore { params: &SignatureParameters, tx: &AtomTxInput, ) -> Result { + let path_parts = params.derivation_path.split('/').collect::>(); + if path_parts.len() < 4 || path_parts[2] != "118'" { + return Err(anyhow!("invalid_sign_path")); + } + let data = Vec::from_hex_auto(&tx.raw_data)?; let hash = sha256(&data); @@ -51,6 +56,7 @@ mod tests { curve: CurveType::SECP256k1, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let mut guard = KeystoreGuard::unlock_by_password(&mut keystore, TEST_PASSWORD).unwrap(); diff --git a/token-core/tcx-btc-kin/src/address.rs b/token-core/tcx-btc-kin/src/address.rs index 19874b93..1fa274e7 100644 --- a/token-core/tcx-btc-kin/src/address.rs +++ b/token-core/tcx-btc-kin/src/address.rs @@ -142,7 +142,7 @@ fn bech32_network(bech32: &str) -> Option<&BtcKinNetwork> { if bech32_prefix.is_some() { let prefix = bech32_prefix.unwrap(); - if (!prefix.is_empty()) { + if !prefix.is_empty() { return BtcKinNetwork::find_by_hrp(prefix); } } @@ -361,6 +361,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let ltc_xprv_str = BtcKinAddress::extended_private_key(&anprv, &coin_info).unwrap(); assert_eq!("xprv9yrdwPSRnvomqFK4u1y5uW2SaXS2Vnr3pAYTjJjbyRZR8p9BwoadRsCxtgUFdAKeRPbwvGRcCSYMV69nNK4N2kadevJ6L5iQVy1SwGKDTHQ", ltc_xprv_str); @@ -381,6 +382,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let ltc_xprv_str = BtcKinAddress::extended_public_key(&anpub, &coin_info).unwrap(); assert_eq!("xpub6JeaAjhtvtjCDnEo4Bjr7uEbGccaHnJtLY4aBnMaAYGjkBRB3fP9XvjcCbNjMiU1n5tt7dYKVgHPGzh3t3W6eLBxavxABTaoQ2jhbiQrfe4", ltc_xprv_str); @@ -473,6 +475,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let address = BtcKinAddress::from_public_key(&pub_key, &coin_info) .unwrap() @@ -510,6 +513,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }) .unwrap(); assert_eq!(account.address, "DQ4tVEqdPWHc1aVBm4Sfwft8XyNRPMEchR"); @@ -541,6 +545,7 @@ mod tests { curve: CurveType::SECP256k1, network: network.to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let account = hd.derive_coin::(&coin_info).unwrap(); assert_eq!(account.ext_pub_key, xpub); @@ -562,6 +567,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "VERSION_0".to_string(), + contract_code: "".to_string(), }; let address = BtcKinAddress::from_public_key(&pub_key, &coin_info) .unwrap() diff --git a/token-core/tcx-btc-kin/src/bch_address.rs b/token-core/tcx-btc-kin/src/bch_address.rs index 38f76ff1..de5800cc 100644 --- a/token-core/tcx-btc-kin/src/bch_address.rs +++ b/token-core/tcx-btc-kin/src/bch_address.rs @@ -197,6 +197,7 @@ mod tests { curve: CurveType::SECP256k1, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let ret = BchAddress::from_public_key(&pk, &wrong_coin_info); assert_eq!(format!("{}", ret.err().unwrap()), "missing_network"); @@ -289,6 +290,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let address = BchAddress::from_public_key(&pub_key, &coin_info) .unwrap() diff --git a/token-core/tcx-btc-kin/src/message.rs b/token-core/tcx-btc-kin/src/message.rs index 26573fc2..d54a2a61 100644 --- a/token-core/tcx-btc-kin/src/message.rs +++ b/token-core/tcx-btc-kin/src/message.rs @@ -109,6 +109,7 @@ impl MessageSigner for Keystore { curve: CurveType::SECP256k1, network: params.network.to_string(), seg_wit: params.seg_wit.to_string(), + contract_code: "".to_string(), }; let address = BtcKinAddress::from_public_key(&public_key, &coin_info)?; @@ -159,6 +160,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "VERSION_0".to_string(), + contract_code: "".to_string(), }; let account = ks.derive_coin::(&coin_info).unwrap(); @@ -183,6 +185,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "P2WPKH".to_string(), + contract_code: "".to_string(), }; let account = ks.derive_coin::(&coin_info).unwrap(); @@ -219,6 +222,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let account = ks.derive_coin::(&coin_info).unwrap(); @@ -256,6 +260,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "VERSION_0".to_string(), + contract_code: "".to_string(), }; let account = ks.derive_coin::(&coin_info).unwrap(); @@ -292,6 +297,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "VERSION_1".to_string(), + contract_code: "".to_string(), }; let account = ks.derive_coin::(&coin_info).unwrap(); diff --git a/token-core/tcx-btc-kin/src/psbt.rs b/token-core/tcx-btc-kin/src/psbt.rs index ff1e9c7c..851eb2e7 100644 --- a/token-core/tcx-btc-kin/src/psbt.rs +++ b/token-core/tcx-btc-kin/src/psbt.rs @@ -446,6 +446,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "VERSION_1".to_string(), + contract_code: "".to_string(), }; let account = hd.derive_coin::(&coin_info).unwrap(); @@ -486,6 +487,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "VERSION_1".to_string(), + contract_code: "".to_string(), }; let account = hd.derive_coin::(&coin_info).unwrap(); diff --git a/token-core/tcx-btc-kin/src/signer.rs b/token-core/tcx-btc-kin/src/signer.rs index c536588a..f00b11ff 100644 --- a/token-core/tcx-btc-kin/src/signer.rs +++ b/token-core/tcx-btc-kin/src/signer.rs @@ -266,6 +266,7 @@ impl> KinTransaction curve: params.curve, network: params.network.clone(), seg_wit: params.seg_wit.clone(), + contract_code: "".to_string(), }; let change_script = if let Some(change_address_index) = self.change_address_index && keystore.derivable() { diff --git a/token-core/tcx-ckb/src/address.rs b/token-core/tcx-ckb/src/address.rs index 3219466e..5e55a976 100644 --- a/token-core/tcx-ckb/src/address.rs +++ b/token-core/tcx-ckb/src/address.rs @@ -105,6 +105,7 @@ mod tests { curve: CurveType::SECP256k1, network: network.to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let pub_key = TypedPublicKey::from_slice( @@ -134,6 +135,7 @@ mod tests { curve: CurveType::SECP256k1, network: network.to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; assert!(CkbAddress::is_valid(address, &coin_info)); } @@ -150,6 +152,7 @@ mod tests { curve: CurveType::SECP256k1, network: network.to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; assert!(!CkbAddress::is_valid(address, &coin_info)); } @@ -171,6 +174,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; assert!(!CkbAddress::is_valid(invalid_address, &coin_info)); } @@ -197,6 +201,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let address = CkbAddress::from_public_key(&pub_key, &coin_info) .unwrap() diff --git a/token-core/tcx-ckb/src/signer.rs b/token-core/tcx-ckb/src/signer.rs index 3f6775f3..7f6b5df4 100644 --- a/token-core/tcx-ckb/src/signer.rs +++ b/token-core/tcx-ckb/src/signer.rs @@ -305,6 +305,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let mut ks = @@ -441,6 +442,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let _account = ks.derive_coin::(&coin_info).unwrap().clone(); @@ -524,6 +526,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let mut ks = @@ -590,6 +593,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let mut ks = @@ -660,6 +664,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let mut ks = @@ -698,6 +703,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let mut ks = @@ -745,6 +751,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let mut ks = @@ -892,6 +899,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let mut ks = diff --git a/token-core/tcx-constants/src/btc_fork_network.rs b/token-core/tcx-constants/src/btc_fork_network.rs index e8b2cab9..74829309 100644 --- a/token-core/tcx-constants/src/btc_fork_network.rs +++ b/token-core/tcx-constants/src/btc_fork_network.rs @@ -255,6 +255,7 @@ mod test { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }) .unwrap(); diff --git a/token-core/tcx-constants/src/coin_info.rs b/token-core/tcx-constants/src/coin_info.rs index ad29c989..c12eea28 100644 --- a/token-core/tcx-constants/src/coin_info.rs +++ b/token-core/tcx-constants/src/coin_info.rs @@ -22,6 +22,7 @@ pub struct CoinInfo { pub network: String, pub seg_wit: String, pub chain_id: String, + pub contract_code: String, } impl Default for CoinInfo { @@ -32,8 +33,8 @@ impl Default for CoinInfo { curve: CurveType::SECP256k1, network: "".to_string(), seg_wit: "".to_string(), - chain_id: "".to_string(), + contract_code: "".to_string(), } } } @@ -48,6 +49,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -56,6 +58,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -64,6 +67,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "P2WPKH".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -72,6 +76,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "P2WPKH".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -80,6 +85,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "VERSION_0".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -88,6 +94,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "VERSION_0".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -96,6 +103,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "VERSION_1".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -104,6 +112,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "VERSION_1".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -112,6 +121,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -120,6 +130,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -128,6 +139,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -136,6 +148,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -144,6 +157,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "P2WPKH".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -152,6 +166,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "P2WPKH".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -160,6 +175,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -168,6 +184,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -176,6 +193,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -184,6 +202,7 @@ lazy_static! { curve: CurveType::SR25519, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -192,6 +211,7 @@ lazy_static! { curve: CurveType::SR25519, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -200,6 +220,7 @@ lazy_static! { curve: CurveType::ED25519, network: "MAINNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -208,6 +229,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -216,6 +238,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -224,6 +247,7 @@ lazy_static! { curve: CurveType::BLS, network: "MAINNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -232,6 +256,7 @@ lazy_static! { curve: CurveType::BLS, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -240,6 +265,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -248,6 +274,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -256,6 +283,7 @@ lazy_static! { curve: CurveType::BLS, network: "MAINNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -264,6 +292,7 @@ lazy_static! { curve: CurveType::BLS, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -272,6 +301,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -280,6 +310,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -288,6 +319,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -296,6 +328,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -304,6 +337,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -312,6 +346,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -320,6 +355,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "P2WPKH".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -328,6 +364,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "P2WPKH".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -336,6 +373,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "VERSION_0".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -344,6 +382,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "VERSION_0".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -352,6 +391,7 @@ lazy_static! { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "VERSION_1".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -360,6 +400,25 @@ lazy_static! { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "VERSION_1".to_string(), + contract_code: "".to_string(), + }, + CoinInfo { + chain_id: "".to_string(), + coin: "TON".to_string(), + derivation_path: "m/44'/607'/0'".to_string(), + curve: CurveType::ED25519, + network: "MAINNET".to_string(), + seg_wit: "".to_string(), + contract_code: "".to_string(), + }, + CoinInfo { + chain_id: "".to_string(), + coin: "TON".to_string(), + derivation_path: "m/44'/607'/0'".to_string(), + curve: CurveType::ED25519, + network: "TESTNET".to_string(), + seg_wit: "".to_string(), + contract_code: "".to_string(), }, ]; diff --git a/token-core/tcx-eth/src/address.rs b/token-core/tcx-eth/src/address.rs index 5f01b2ce..c298d4d8 100644 --- a/token-core/tcx-eth/src/address.rs +++ b/token-core/tcx-eth/src/address.rs @@ -133,6 +133,7 @@ mod test { curve: CurveType::SECP256k1, network: "testnet".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let address = EthAddress::from_public_key(&typed_public_key, &coin_info).unwrap(); assert_eq!( @@ -214,6 +215,7 @@ mod test { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let address = EthAddress::from_public_key(&pub_key, &coin_info) .unwrap() diff --git a/token-core/tcx-filecoin/src/address.rs b/token-core/tcx-filecoin/src/address.rs index 7a57b526..d84774f7 100644 --- a/token-core/tcx-filecoin/src/address.rs +++ b/token-core/tcx-filecoin/src/address.rs @@ -167,6 +167,7 @@ mod tests { curve: CurveType::BLS, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; for (input, expected) in test_cases { @@ -263,6 +264,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; for (input, expected) in test_cases { diff --git a/token-core/tcx-keystore/src/keystore/hd.rs b/token-core/tcx-keystore/src/keystore/hd.rs index 27fbab46..0912fb3b 100644 --- a/token-core/tcx-keystore/src/keystore/hd.rs +++ b/token-core/tcx-keystore/src/keystore/hd.rs @@ -347,6 +347,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -355,6 +356,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "P2WPKH".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -363,6 +365,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "VERSION_0".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -371,6 +374,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "VERSION_1".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -379,6 +383,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -387,6 +392,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "P2WPKH".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -395,6 +401,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "VERSION_0".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -403,6 +410,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "VERSION_1".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -411,6 +419,7 @@ mod tests { curve: CurveType::ED25519, network: "MAINNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, ]; @@ -444,6 +453,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let _ = keystore .unlock(&Key::Password(TEST_PASSWORD.to_owned())) @@ -553,6 +563,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let _ = keystore .unlock(&Key::Password(TEST_PASSWORD.to_owned())) @@ -620,6 +631,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let _ = keystore .unlock(&Key::Password(TEST_PASSWORD.to_owned())) @@ -664,6 +676,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; m.iter(|| { @@ -688,6 +701,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -696,6 +710,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -704,6 +719,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -712,6 +728,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "VERSION_0".to_string(), + contract_code: "".to_string(), }, CoinInfo { chain_id: "".to_string(), @@ -720,6 +737,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "VERSION_1".to_string(), + contract_code: "".to_string(), }, ]; @@ -752,6 +770,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let acc = keystore.derive_coin::(&coin_info).unwrap(); assert_eq!(acc.ext_pub_key, "tpubDD7tXK8KeQ3YY83yWq755fHY2JW8Ha8Q765tknUM5rSvjPcGWfUppDFMpQ1ScziKfW3ZNtZvAD7M3u7bSs7HofjTD3KP3YxPK7X6hwV8Rk2"); @@ -763,6 +782,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let acc = keystore.derive_coin::(&coin_info).unwrap(); assert_eq!( @@ -786,6 +806,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let acc = keystore.derive_coin::(&coin_info).unwrap(); assert_eq!(acc.ext_pub_key, "xpub6CatWdiZiodmUeTDp8LT5or8nmbKNcuyvz7WyksVFkKB4RHwCD3XyuvPEbvqAQY3rAPshWcMLoP2fMFMKHPJ4ZeZXYVUhLv1VMrjPC7PW6V"); diff --git a/token-core/tcx-keystore/src/keystore/mod.rs b/token-core/tcx-keystore/src/keystore/mod.rs index ab04b1b6..ee05adda 100644 --- a/token-core/tcx-keystore/src/keystore/mod.rs +++ b/token-core/tcx-keystore/src/keystore/mod.rs @@ -903,6 +903,7 @@ pub(crate) mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let k1_pub_key = Secp256k1PublicKey::from_slice( @@ -976,6 +977,7 @@ pub(crate) mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let k1_pub_key = Secp256k1PublicKey::from_slice( @@ -1021,6 +1023,7 @@ pub(crate) mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let typed_pk = TypedDeterministicPublicKey::from_hex(CurveType::SECP256k1, "03a25f12b68000000044efc688fe25a1a677765526ed6737b4bfcfb0122589caab7ca4b223ffa9bb37029d23439ecb195eb06a0d44a608960d18702fd97e19c53451f0548f568207af77").unwrap(); diff --git a/token-core/tcx-keystore/src/keystore/private.rs b/token-core/tcx-keystore/src/keystore/private.rs index 6a3cc333..c99af737 100644 --- a/token-core/tcx-keystore/src/keystore/private.rs +++ b/token-core/tcx-keystore/src/keystore/private.rs @@ -271,6 +271,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }]; let excepts = ["0280c98b8ea7cab630defb0c09a4295c2193cdee016c1d5b9b0cb18572b9c370fe"]; diff --git a/token-core/tcx-libs/tonlib-core/Cargo.lock b/token-core/tcx-libs/tonlib-core/Cargo.lock new file mode 100644 index 00000000..253a5623 --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/Cargo.lock @@ -0,0 +1,336 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.168" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" + +[[package]] +name = "nacl" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30aefc44d813c51b5e7952950e87c17f2e0e1a3274d63c8281a701e05323d548" + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tonlib-core" +version = "0.21.1" +dependencies = [ + "base64", + "bitstream-io", + "crc", + "hex", + "hmac", + "lazy_static", + "nacl", + "num-bigint", + "num-traits", + "pbkdf2", + "serde", + "serde_json", + "sha2", + "thiserror", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" diff --git a/token-core/tcx-libs/tonlib-core/Cargo.toml b/token-core/tcx-libs/tonlib-core/Cargo.toml new file mode 100644 index 00000000..f0a7b062 --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "tonlib-core" +description = "Rust SDK for The Open Network" +version = "0.21.1" +edition = "2021" +license = "MIT" +repository = "https://github.com/ston-fi/tonlib-rs" +resolver = "2" + +[dependencies] +base64 = "0.22" +bitstream-io = "=2.3.0" +crc = "=3.2.1" +hex = "=0.4.3" +hmac = {version = "=0.12.1", features = ["std"]} +lazy_static = "=1.4.0" +num-bigint = { version = "=0.4.3", features = ["serde"] } +num-traits = "=0.2.15" +serde = { version = "=1.0.147", features = ["derive"] } +serde_json = "=1.0.89" +sha2 = "=0.10.6" +thiserror = "=1.0.56" \ No newline at end of file diff --git a/token-core/tcx-libs/tonlib-core/README.md b/token-core/tcx-libs/tonlib-core/README.md new file mode 100644 index 00000000..6febe97f --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/README.md @@ -0,0 +1,94 @@ +# Rust SDK for The Open Network + +Rust SDK for [The Open Network](https://ton.org/) + +## Features + +* Support parsing and generation of Cell and BagOfCell for more convenient interaction with data structures +* Support of existing Wallet versions +* Derive wallet address +* Support of TON Mnemonics +* NaCl-compatible Ed25519 signing of transactions + +## Usage + +To use this library in your Rust application, add the following to your Cargo.toml file: + +```toml +[dependencies] +tonlib-core = "version" +``` + +Then, in your Rust code, you can import the library with: + +```rust +use tonlib_core; +``` + +## Package contents + +### Cell + +Data structures and helpers for building and parsing Cell and Bag of Cells. See the documentation on [ton.org ](https://docs.ton.org/develop/data-formats/cell-boc)for details. + +### Message + +Data structures, builders, and parsers for Message +See the documentation on [ton.org ](https://docs.ton.org/develop/smart-contracts/messages)for details. + +Includes standard messages for Jetton, NFT, and Soulbound NFT, specified by [TON Enhancement Proposal](https://github.com/ton-blockchain/TEPs/blob/master/text/0001-tep-lifecycle.md). + +### Mnemonic + +Data structure to store mnemonic. + +### Types + +Data structures for storage and easy conversion of [Ton Smart-contract Address](https://docs.ton.org/learn/overviews/addresses) and [Ton Transaction Id](https://docs.ton.org/develop/data-formats/transaction-layout#transaction) + + +### Wallet + +Data structure for deriving wallet addresses. + +## Usage examples + +### Cell + +Creating a `Cell` and writing data to it: + +``` rust +use anyhow::anyhow; +use tonlib_core::TonAddress; +use tonlib_core::cell::CellBuilder; + +fn write_cell() -> anyhow::Result<()> { +let mut writer = CellBuilder::new(); +let addr = TonAddress::from_base64_url("EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR")?; +let cell = writer + .store_u32(32, 0xFAD45AADu32)? + .store_bit(true)? + .store_u8(8, 234u8)? + .store_slice(&[0xFA, 0xD4, 0x5A, 0xAD, 0xAA, 0x12, 0xFF, 0x45])? + .store_address(&addr)? + .store_string("Hello, TON")? + .build()?; + # Ok(()) +} +``` + + Reading data from a `Cell`: + +```rust +use tonlib_core::cell::Cell; +fn read_cell(cell: Cell) -> anyhow::Result<()> { + let mut reader = cell.parser(); + let u32_value = reader.load_u32(32)?; + let bit_value = reader.load_bit()?; + let u8_value = reader.load_u8(8)?; + let bytes_value = reader.load_bytes(8)?; + let address_value = reader.load_address()?; + let str_value = reader.ensure_empty()?; + Ok(()) +} +``` \ No newline at end of file diff --git a/token-core/tcx-libs/tonlib-core/src/cell.rs b/token-core/tcx-libs/tonlib-core/src/cell.rs new file mode 100644 index 00000000..fd2b6353 --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/src/cell.rs @@ -0,0 +1,385 @@ +use std::fmt::{Debug, Formatter}; +use std::hash::Hash; +use std::sync::Arc; +use std::{fmt, io}; + +pub use bag_of_cells::*; +use base64::Engine; +use bitstream_io::{BigEndian, BitWrite, BitWriter}; +pub use builder::*; +pub use error::*; +use hmac::digest::Digest; +use lazy_static::lazy_static; +pub use parser::*; +pub use raw::*; +use sha2::Sha256; +pub use state_init::*; + +use crate::cell::cell_type::CellType; +use crate::cell::level_mask::LevelMask; +use crate::types::DEFAULT_CELL_HASH; +use crate::TonHash; + +mod bag_of_cells; +mod builder; + +mod cell_type; +mod error; +mod level_mask; +mod parser; +mod raw; +mod state_init; +const DEPTH_BYTES: usize = 2; +const MAX_LEVEL: u8 = 3; + +pub type ArcCell = Arc; + +lazy_static! { + pub static ref EMPTY_CELL: Cell = Cell::default(); + pub static ref EMPTY_ARC_CELL: ArcCell = Arc::new(Cell::default()); +} + +#[derive(PartialEq, Eq, Clone, Hash)] +pub struct Cell { + data: Vec, + bit_len: usize, + references: Vec, + cell_type: CellType, + level_mask: LevelMask, + hashes: [TonHash; 4], + depths: [u16; 4], +} + +impl Cell { + pub fn new( + //=== + data: Vec, + bit_len: usize, + references: Vec, + is_exotic: bool, + ) -> Result { + let cell_type = if is_exotic { + CellType::determine_exotic_cell_type(&data)? + } else { + CellType::Ordinary + }; + + cell_type.validate(&data, bit_len, &references)?; + let level_mask = cell_type.level_mask(&data, bit_len, &references)?; + let (hashes, depths) = + calculate_hashes_and_depths(cell_type, &data, bit_len, &references, level_mask)?; + + let result = Self { + data, + bit_len, + references, + level_mask, + cell_type, + hashes, + depths, + }; + + Ok(result) + } + + pub fn parser(&self) -> CellParser { + CellParser::new(self.bit_len, &self.data, &self.references) + } + + pub fn get_depth(&self, level: u8) -> u16 { + self.depths[level.min(3) as usize] + } + + pub fn cell_hash(&self) -> TonHash { + //== + self.get_hash(MAX_LEVEL) + } + + pub fn get_hash(&self, level: u8) -> TonHash { + //==== + self.hashes[level.min(3) as usize] + } + + pub fn to_arc(self) -> ArcCell { + Arc::new(self) + } +} + +impl Debug for Cell { + //== + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let t = match self.cell_type { + CellType::Ordinary | CellType::Library => 'x', + CellType::PrunedBranch | CellType::MerkleProof => 'p', + CellType::MerkleUpdate => 'u', + }; + + // Our completion tag ONLY shows that the last byte is incomplete + // It does not correspond to real completion tag defined in + // p1.0.2 of https://docs.ton.org/tvm.pdf for details + // Null termination of bit-string defined in that document is omitted for clarity + let completion_tag = if self.bit_len % 8 != 0 { "_" } else { "" }; + writeln!( + f, + "Cell {}{{ data: [{}{}]\n, bit_len: {}\n, references: [", + t, + self.data + .iter() + .map(|&byte| format!("{:02X}", byte)) + .collect::>() + .join(""), + completion_tag, + self.bit_len, + )?; + + for reference in &self.references { + writeln!( + f, + " {}\n", + format!("{:?}", reference).replace('\n', "\n ") + )?; + } + + write!( + f, + "]\n cell_type: {:?}\n level_mask: {:?}\n hashes {:?}\n depths {:?}\n }}", + self.cell_type, + self.level_mask, + self.hashes + .iter() + .map(|h| h + .iter() + .map(|&byte| format!("{:02X}", byte)) + .collect::>() + .join("")) + .collect::>(), + self.depths + ) + } +} + +impl Default for Cell { + fn default() -> Self { + Self { + data: Default::default(), + bit_len: Default::default(), + references: Default::default(), + cell_type: Default::default(), + level_mask: Default::default(), + hashes: [DEFAULT_CELL_HASH; 4], + depths: Default::default(), + } + } +} + +fn get_repr_for_data( + //=== + original_data_bit_len: usize, + (data, data_bit_len): (&[u8], usize), + refs: &[ArcCell], + level_mask: LevelMask, + level: u8, + cell_type: CellType, +) -> Result, TonCellError> { + // Allocate + let data_len = data.len(); + // descriptors + data + (hash + depth) * refs_count + let buffer_len = 2 + data_len + (32 + 2) * refs.len(); + + let mut writer = BitWriter::endian(Vec::with_capacity(buffer_len), BigEndian); + let d1 = get_refs_descriptor(cell_type, refs, level_mask.apply(level).mask())?; + let d2 = get_bits_descriptor(original_data_bit_len)?; + + // Write descriptors + writer.write(8, d1).map_cell_parser_error()?; + writer.write(8, d2).map_cell_parser_error()?; + // Write main data + write_data(&mut writer, data, data_bit_len).map_cell_parser_error()?; + // Write ref data + write_ref_depths(&mut writer, refs, cell_type, level)?; + write_ref_hashes(&mut writer, refs, cell_type, level)?; + + let result = writer + .writer() + .ok_or_else(|| TonCellError::cell_builder_error("Stream for cell repr is not byte-aligned")) + .map(|b| b.to_vec()); + + result +} + +/// This function replicates unknown logic of resolving cell data +/// https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/vm/cells/DataCell.cpp#L214 +fn calculate_hashes_and_depths( + //=== + cell_type: CellType, + data: &[u8], + bit_len: usize, + references: &[ArcCell], + level_mask: LevelMask, +) -> Result<([TonHash; 4], [u16; 4]), TonCellError> { + let hash_count = if cell_type == CellType::PrunedBranch { + 1 + } else { + level_mask.hash_count() + }; + + let total_hash_count = level_mask.hash_count(); + let hash_i_offset = total_hash_count - hash_count; + + let mut depths: Vec = Vec::with_capacity(hash_count); + let mut hashes: Vec = Vec::with_capacity(hash_count); + + // Iterate through significant levels + for (hash_i, level_i) in (0..=level_mask.level()) + .filter(|&i| level_mask.is_significant(i)) + .enumerate() + { + if hash_i < hash_i_offset { + continue; + } + + let (current_data, current_bit_len) = if hash_i == hash_i_offset { + (data, bit_len) + } else { + let previous_hash = hashes + .get(hash_i - hash_i_offset - 1) + .ok_or_else(|| TonCellError::InternalError("Can't get right hash".to_owned()))?; + (previous_hash.as_slice(), 256) + }; + + // Calculate Depth + let depth = if references.is_empty() { + 0 + } else { + let max_ref_depth = references.iter().fold(0, |max_depth, reference| { + let child_depth = cell_type.child_depth(reference, level_i); + max_depth.max(child_depth) + }); + + max_ref_depth + 1 + }; + + // Calculate Hash + let repr = get_repr_for_data( + bit_len, + (current_data, current_bit_len), + references, + level_mask, + level_i, + cell_type, + )?; + let hash = Sha256::new_with_prefix(repr).finalize()[..] + .try_into() + .map_err(|error| { + TonCellError::InternalError(format!( + "Can't get [u8; 32] from finalized hash with error: {error}" + )) + })?; + + depths.push(depth); + hashes.push(hash); + } + + cell_type.resolve_hashes_and_depths(hashes, depths, data, bit_len, level_mask) +} + +/// Calculates d1 descriptor for cell +/// See https://docs.ton.org/tvm.pdf 3.1.4 for details +fn get_refs_descriptor( + cell_type: CellType, + references: &[ArcCell], + level_mask: u32, +) -> Result { + if references.len() > MAX_CELL_REFERENCES { + Err(TonCellError::InvalidCellData( + "Cell should not contain more than 4 references".to_string(), + )) + } else if level_mask > MAX_LEVEL_MASK { + Err(TonCellError::InvalidCellData( + "Cell level mask can not be higher than 3".to_string(), + )) + } else { + let cell_type_var = (cell_type != CellType::Ordinary) as u8; + let d1 = references.len() as u8 + 8 * cell_type_var + level_mask as u8 * 32; + Ok(d1) + } +} + +/// Calculates d2 descriptor for cell +/// See https://docs.ton.org/tvm.pdf 3.1.4 for details +fn get_bits_descriptor(bit_len: usize) -> Result { + if bit_len > MAX_CELL_BITS { + Err(TonCellError::InvalidCellData( + "Cell data length should not contain more than 1023 bits".to_string(), + )) + } else { + let d2 = (bit_len / 8 + (bit_len + 7) / 8) as u8; + Ok(d2) + } +} + +fn write_data( + writer: &mut BitWriter, BigEndian>, + data: &[u8], + bit_len: usize, +) -> Result<(), io::Error> { + let data_len = data.len(); + let rest_bits = bit_len % 8; + let full_bytes = rest_bits == 0; + + if !full_bytes { + writer.write_bytes(&data[..data_len - 1])?; + let last_byte = data[data_len - 1]; + let l = last_byte | 1 << (8 - rest_bits - 1); + writer.write(8, l)?; + } else { + writer.write_bytes(data)?; + } + + Ok(()) +} + +fn write_ref_depths( + writer: &mut BitWriter, BigEndian>, + refs: &[ArcCell], + parent_cell_type: CellType, + level: u8, +) -> Result<(), TonCellError> { + for reference in refs { + let child_depth = if matches!( + parent_cell_type, + CellType::MerkleProof | CellType::MerkleUpdate + ) { + reference.get_depth(level + 1) + } else { + reference.get_depth(level) + }; + + writer.write(8, child_depth / 256).map_cell_parser_error()?; + writer.write(8, child_depth % 256).map_cell_parser_error()?; + } + + Ok(()) +} + +fn write_ref_hashes( + writer: &mut BitWriter, BigEndian>, + refs: &[ArcCell], + parent_cell_type: CellType, + level: u8, +) -> Result<(), TonCellError> { + for reference in refs { + let child_hash = if matches!( + parent_cell_type, + CellType::MerkleProof | CellType::MerkleUpdate + ) { + reference.get_hash(level + 1) + } else { + reference.get_hash(level) + }; + + writer.write_bytes(&child_hash).map_cell_parser_error()?; + } + + Ok(()) +} diff --git a/token-core/tcx-libs/tonlib-core/src/cell/bag_of_cells.rs b/token-core/tcx-libs/tonlib-core/src/cell/bag_of_cells.rs new file mode 100644 index 00000000..c11a31b4 --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/src/cell/bag_of_cells.rs @@ -0,0 +1,71 @@ +use std::sync::Arc; + +use base64::engine::general_purpose::STANDARD; + +use crate::cell::*; + +#[derive(PartialEq, Eq, Debug, Clone, Hash)] +pub struct BagOfCells { + pub roots: Vec, +} + +impl BagOfCells { + pub fn new(roots: &[ArcCell]) -> BagOfCells { + BagOfCells { + roots: roots.to_vec(), + } + } + + pub fn single_root(&self) -> Result<&ArcCell, TonCellError> { + let root_count = self.roots.len(); + if root_count == 1 { + Ok(&self.roots[0]) + } else { + Err(TonCellError::CellParserError(format!( + "Single root expected, got {}", + root_count + ))) + } + } + + pub fn parse(serial: &[u8]) -> Result { + let raw = RawBagOfCells::parse(serial)?; + let num_cells = raw.cells.len(); + let mut cells: Vec = Vec::with_capacity(num_cells); + + for (cell_index, raw_cell) in raw.cells.into_iter().enumerate().rev() { + let mut references = Vec::with_capacity(raw_cell.references.len()); + for ref_index in &raw_cell.references { + if *ref_index <= cell_index { + return Err(TonCellError::boc_deserialization_error( + "References to previous cells are not supported", + )); + } + references.push(cells[num_cells - 1 - ref_index].clone()); + } + + let cell = Cell::new( + raw_cell.data, + raw_cell.bit_len, + references, + raw_cell.is_exotic, + ) + .map_boc_deserialization_error()?; + cells.push(cell.to_arc()); + } + + let roots = raw + .roots + .into_iter() + .map(|r| &cells[num_cells - 1 - r]) + .map(Arc::clone) + .collect(); + + Ok(BagOfCells { roots }) + } + + pub fn parse_base64(base64: &str) -> Result { + let bin = STANDARD.decode(base64).map_boc_deserialization_error()?; + Self::parse(&bin) + } +} diff --git a/token-core/tcx-libs/tonlib-core/src/cell/builder.rs b/token-core/tcx-libs/tonlib-core/src/cell/builder.rs new file mode 100644 index 00000000..5c79f618 --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/src/cell/builder.rs @@ -0,0 +1,186 @@ +use bitstream_io::{BigEndian, BitWrite, BitWriter}; +use num_bigint::BigUint; +use num_traits::Zero; + +use crate::cell::error::{MapTonCellError, TonCellError}; +use crate::cell::{ArcCell, Cell}; + +pub(crate) const MAX_CELL_BITS: usize = 1023; +pub(crate) const MAX_CELL_REFERENCES: usize = 4; +pub(crate) const MAX_LEVEL_MASK: u32 = 3; + +pub struct CellBuilder { + bit_writer: BitWriter, BigEndian>, + bits_to_write: usize, + references: Vec, + is_cell_exotic: bool, +} + +#[derive(Clone, Debug, PartialEq, Copy)] +pub enum EitherCellLayout { + Native, + ToRef, + ToCell, +} + +impl CellBuilder { + pub fn new() -> CellBuilder { + let bit_writer = BitWriter::endian(Vec::new(), BigEndian); + CellBuilder { + bit_writer, + bits_to_write: 0, + references: Vec::new(), + is_cell_exotic: false, + } + } + + pub fn store_bit(&mut self, val: bool) -> Result<&mut Self, TonCellError> { + self.bit_writer.write_bit(val).map_cell_builder_error()?; + self.bits_to_write += 1; + Ok(self) + } + + pub fn store_u8(&mut self, bit_len: usize, val: u8) -> Result<&mut Self, TonCellError> { + self.bit_writer + .write(bit_len as u32, val) + .map_cell_builder_error()?; + self.bits_to_write += bit_len; + Ok(self) + } + + pub fn store_u32(&mut self, bit_len: usize, val: u32) -> Result<&mut Self, TonCellError> { + self.bit_writer + .write(bit_len as u32, val) + .map_cell_builder_error()?; + self.bits_to_write += bit_len; + Ok(self) + } + + pub fn store_i32(&mut self, bit_len: usize, val: i32) -> Result<&mut Self, TonCellError> { + self.bit_writer + .write(bit_len as u32, val) + .map_cell_builder_error()?; + self.bits_to_write += bit_len; + Ok(self) + } + + pub fn store_u64(&mut self, bit_len: usize, val: u64) -> Result<&mut Self, TonCellError> { + self.bit_writer + .write(bit_len as u32, val) + .map_cell_builder_error()?; + self.bits_to_write += bit_len; + Ok(self) + } + + pub fn store_i64(&mut self, bit_len: usize, val: i64) -> Result<&mut Self, TonCellError> { + self.bit_writer + .write(bit_len as u32, val) + .map_cell_builder_error()?; + self.bits_to_write += bit_len; + Ok(self) + } + + pub fn store_uint(&mut self, bit_len: usize, val: &BigUint) -> Result<&mut Self, TonCellError> { + let minimum_bits_needed = if val.is_zero() { 1 } else { val.bits() } as usize; + if minimum_bits_needed > bit_len { + return Err(TonCellError::cell_builder_error(format!( + "Value {} doesn't fit in {} bits (takes {} bits)", + val, bit_len, minimum_bits_needed + ))); + } + + let value_bytes = val.to_bytes_be(); + let first_byte_bit_size = bit_len - (value_bytes.len() - 1) * 8; + + for _ in 0..(first_byte_bit_size - 1) / 32 { + // fill full-bytes padding + self.store_u32(32, 0u32)?; + } + + // fill first byte with required size + if first_byte_bit_size % 32 == 0 { + self.store_u32(32, value_bytes[0] as u32)?; + } else { + self.store_u32(first_byte_bit_size % 32, value_bytes[0] as u32) + .map_cell_builder_error()?; + } + + // fill remaining bytes + for byte in value_bytes.iter().skip(1) { + self.store_u8(8, *byte).map_cell_builder_error()?; + } + Ok(self) + } + + pub fn store_byte(&mut self, val: u8) -> Result<&mut Self, TonCellError> { + self.store_u8(8, val) + } + + pub fn store_slice(&mut self, slice: &[u8]) -> Result<&mut Self, TonCellError> { + for val in slice { + self.store_byte(*val)?; + } + Ok(self) + } + + /// Adds reference to an existing `Cell`. + /// + /// The reference is passed as `ArcCell` so it might be references from other cells. + pub fn store_reference(&mut self, cell: &ArcCell) -> Result<&mut Self, TonCellError> { + let ref_count = self.references.len() + 1; + if ref_count > 4 { + return Err(TonCellError::cell_builder_error(format!( + "Cell must contain at most 4 references, got {}", + ref_count + ))); + } + self.references.push(cell.clone()); + Ok(self) + } + + pub fn remaining_bits(&self) -> usize { + MAX_CELL_BITS - self.bits_to_write + } + + pub fn build(&mut self) -> Result { + let mut trailing_zeros = 0; + while !self.bit_writer.byte_aligned() { + self.bit_writer.write_bit(false).map_cell_builder_error()?; + trailing_zeros += 1; + } + + if let Some(vec) = self.bit_writer.writer() { + let bit_len = vec.len() * 8 - trailing_zeros; + if bit_len > MAX_CELL_BITS { + return Err(TonCellError::cell_builder_error(format!( + "Cell must contain at most {} bits, got {}", + MAX_CELL_BITS, bit_len + ))); + } + let ref_count = self.references.len(); + if ref_count > MAX_CELL_REFERENCES { + return Err(TonCellError::cell_builder_error(format!( + "Cell must contain at most 4 references, got {}", + ref_count + ))); + } + + Cell::new( + vec.clone(), + bit_len, + self.references.clone(), + self.is_cell_exotic, + ) + } else { + Err(TonCellError::CellBuilderError( + "Stream is not byte-aligned".to_string(), + )) + } + } +} + +impl Default for CellBuilder { + fn default() -> Self { + Self::new() + } +} diff --git a/token-core/tcx-libs/tonlib-core/src/cell/cell_type.rs b/token-core/tcx-libs/tonlib-core/src/cell/cell_type.rs new file mode 100644 index 00000000..27fefa58 --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/src/cell/cell_type.rs @@ -0,0 +1,381 @@ +use std::cmp::PartialEq; +use std::io; +use std::io::Cursor; + +use bitstream_io::{BigEndian, ByteRead, ByteReader}; + +use crate::cell::level_mask::LevelMask; +use crate::cell::{ArcCell, Cell, MapTonCellError, TonCellError, DEPTH_BYTES, MAX_LEVEL}; +use crate::types::{TON_HASH_BYTES, ZERO_HASH}; +use crate::TonHash; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub(crate) enum CellType { + #[default] + Ordinary, + PrunedBranch, + Library, + MerkleProof, + MerkleUpdate, +} + +#[derive(Debug, Clone)] +struct Pruned { + hash: TonHash, + depth: u16, +} + +impl CellType { + pub(crate) fn determine_exotic_cell_type(data: &[u8]) -> Result { + let Some(type_byte) = data.first() else { + return Err(TonCellError::InvalidExoticCellData( + "Not enough data for an exotic cell".to_owned(), + )); + }; + + let cell_type = match type_byte { + 1 => CellType::PrunedBranch, + 2 => CellType::Library, + 3 => CellType::MerkleProof, + 4 => CellType::MerkleUpdate, + cell_type => { + return Err(TonCellError::InvalidExoticCellData(format!( + "Invalid first byte in exotic cell data: {}", + cell_type + ))) + } + }; + Ok(cell_type) + } + + pub(crate) fn validate( + &self, + data: &[u8], + bit_len: usize, + references: impl AsRef<[ArcCell]>, + ) -> Result<(), TonCellError> { + match self { + CellType::Ordinary => Ok(()), + CellType::PrunedBranch => self.validate_exotic_pruned(data, bit_len, references), + CellType::Library => self.validate_library(bit_len), + CellType::MerkleProof => self.validate_merkle_proof(data, bit_len, references), + CellType::MerkleUpdate => self.validate_merkle_update(data, bit_len, references), + } + } + + pub(crate) fn level_mask( + &self, + cell_data: &[u8], + cell_data_bit_len: usize, + references: &[ArcCell], + ) -> Result { + let result = match self { + CellType::Ordinary => references + .iter() + .fold(LevelMask::new(0), |level_mask, reference| { + level_mask.apply_or(reference.level_mask) + }), + CellType::PrunedBranch => self.pruned_level_mask(cell_data, cell_data_bit_len)?, + CellType::Library => LevelMask::new(0), + CellType::MerkleProof => references[0].level_mask.shift_right(), + CellType::MerkleUpdate => references[0] + .level_mask + .apply_or(references[1].level_mask) + .shift_right(), + }; + + Ok(result) + } + + pub(crate) fn child_depth(&self, child: &Cell, level: u8) -> u16 { + if matches!(self, CellType::MerkleProof | CellType::MerkleUpdate) { + child.get_depth(level + 1) + } else { + child.get_depth(level) + } + } + + pub(crate) fn resolve_hashes_and_depths( + &self, + hashes: Vec, + depths: Vec, + data: &[u8], + bit_len: usize, + level_mask: LevelMask, + ) -> Result<([TonHash; 4], [u16; 4]), TonCellError> { + let mut resolved_hashes = [ZERO_HASH; 4]; + let mut resolved_depths = [0; 4]; + + for i in 0..4 { + let hash_index = level_mask.apply(i).hash_index(); + + let (hash, depth) = if self == &CellType::PrunedBranch { + let this_hash_index = level_mask.hash_index(); + if hash_index != this_hash_index { + let pruned = self + .pruned(data, bit_len, level_mask) + .map_cell_builder_error()?; + (pruned[hash_index].hash, pruned[hash_index].depth) + } else { + (hashes[0], depths[0]) + } + } else { + (hashes[hash_index], depths[hash_index]) + }; + + resolved_hashes[i as usize] = hash; + resolved_depths[i as usize] = depth; + } + + Ok((resolved_hashes, resolved_depths)) + } + + fn validate_exotic_pruned( + &self, + data: &[u8], + bit_len: usize, + references: impl AsRef<[ArcCell]>, + ) -> Result<(), TonCellError> { + if !references.as_ref().is_empty() { + return Err(TonCellError::InvalidExoticCellData(format!( + "Pruned Branch cell can't have refs, got {}", + references.as_ref().len() + ))); + } + + if bit_len < 16 { + return Err(TonCellError::InvalidExoticCellData( + "Not enough data for a PrunnedBranch special cell".to_owned(), + )); + } + + if !self.is_config_proof(bit_len) { + let level_mask = self.pruned_level_mask(data, bit_len)?; + let level = level_mask.level(); + + if level == 0 || level > MAX_LEVEL { + return Err(TonCellError::InvalidExoticCellData(format!( + "Pruned Branch cell level must be >= 1 and <= 3, got {}/{}", + level_mask.level(), + level_mask.mask() + ))); + } + + let expected_size: usize = + (2 + level_mask.apply(level - 1).hash_count() * (TON_HASH_BYTES + DEPTH_BYTES)) * 8; + + if bit_len != expected_size { + return Err(TonCellError::InvalidExoticCellData(format!( + "Pruned branch cell must have exactly {expected_size} bits, got {bit_len}" + ))); + } + } + + Ok(()) + } + + fn validate_library(&self, bit_len: usize) -> Result<(), TonCellError> { + const SIZE: usize = (1 + TON_HASH_BYTES) * 8; + + if bit_len != SIZE { + return Err(TonCellError::InvalidExoticCellData(format!( + "Pruned branch cell must have exactly {SIZE} bits, got {bit_len}" + ))); + } + + Ok(()) + } + + fn validate_merkle_proof( + &self, + data: &[u8], + bit_len: usize, + references: impl AsRef<[ArcCell]>, + ) -> Result<(), TonCellError> { + let references = references.as_ref(); + // type + hash + depth + const SIZE: usize = (1 + TON_HASH_BYTES + DEPTH_BYTES) * 8; + + if bit_len != SIZE { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Proof cell must have exactly (8 + 256 + 16) bits, got {bit_len}" + ))); + } + + if references.as_ref().len() != 1 { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Proof cell must have exactly 1 ref, got {}", + references.as_ref().len() + ))); + } + + let proof_hash: [u8; TON_HASH_BYTES] = + data[1..(1 + TON_HASH_BYTES)].try_into().map_err(|err| { + TonCellError::InvalidExoticCellData(format!( + "Can't get proof hash bytes from cell data, {}", + err + )) + })?; + let proof_depth_bytes = data[(1 + TON_HASH_BYTES)..(1 + TON_HASH_BYTES + 2)] + .try_into() + .map_err(|err| { + TonCellError::InvalidExoticCellData(format!( + "Can't get proof depth bytes from cell data, {}", + err + )) + })?; + let proof_depth = u16::from_be_bytes(proof_depth_bytes); + let ref_hash = references[0].get_hash(0); + let ref_depth = references[0].get_depth(0); + + if proof_depth != ref_depth { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Proof cell ref depth must be exactly {proof_depth}, got {ref_depth}" + ))); + } + + if proof_hash != ref_hash { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Proof cell ref hash must be exactly {proof_hash:?}, got {ref_hash:?}" + ))); + } + + Ok(()) + } + + fn validate_merkle_update( + &self, + data: &[u8], + bit_len: usize, + references: impl AsRef<[ArcCell]>, + ) -> Result<(), TonCellError> { + let references = references.as_ref(); + // type + hash + hash + depth + depth + const SIZE: usize = 8 + (2 * (256 + 16)); + + if bit_len != SIZE { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Update cell must have exactly (8 + 256 + 16) bits, got {bit_len}" + ))); + } + + if references.len() != 2 { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Update cell must have exactly 2 refs, got {}", + references.len() + ))); + } + + let proof_hash1: TonHash = data[1..33].try_into().map_err(|err| { + TonCellError::InvalidExoticCellData(format!( + "Can't get proof hash bytes 1 from cell data, {}", + err + )) + })?; + let proof_hash2: TonHash = data[33..65].try_into().map_err(|err| { + TonCellError::InvalidExoticCellData(format!( + "Can't get proof hash bytes 2 from cell data, {}", + err + )) + })?; + let proof_depth_bytes1 = data[65..67].try_into().map_err(|err| { + TonCellError::InvalidExoticCellData(format!( + "Can't get proof depth bytes 1 from cell data, {}", + err + )) + })?; + let proof_depth_bytes2 = data[67..69].try_into().map_err(|err| { + TonCellError::InvalidExoticCellData(format!( + "Can't get proof depth bytes 2 from cell data, {}", + err + )) + })?; + let proof_depth1 = u16::from_be_bytes(proof_depth_bytes1); + let proof_depth2 = u16::from_be_bytes(proof_depth_bytes2); + + let ref_hash1 = references[0].get_hash(0); + let ref_depth1 = references[0].get_depth(0); + let ref_hash2 = references[1].get_hash(0); + let ref_depth2 = references[1].get_depth(0); + + if proof_depth1 != ref_depth1 { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Proof cell ref depth 1 must be exactly {proof_depth1}, got {ref_depth1}" + ))); + } + + if proof_hash1 != ref_hash1 { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Proof cell ref hash 1 must be exactly {proof_hash1:?}, got {ref_hash1:?}" + ))); + } + + if proof_depth2 != ref_depth2 { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Proof cell ref depth 2 must be exactly {proof_depth2}, got {ref_depth2}" + ))); + } + + if proof_hash2 != ref_hash2 { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Proof cell ref hash 2 must be exactly {proof_hash2:?}, got {ref_hash2:?}" + ))); + } + + Ok(()) + } + + fn pruned_level_mask(&self, data: &[u8], bit_len: usize) -> Result { + if data.len() < 5 { + return Err(TonCellError::InvalidExoticCellData(format!( + "Pruned Branch cell date can't be shorter than 5 bytes, got {}", + data.len() + ))); + } + + let level_mask = if self.is_config_proof(bit_len) { + LevelMask::new(1) + } else { + let mask_byte = data[1]; + LevelMask::new(mask_byte as u32) + }; + + Ok(level_mask) + } + + fn pruned( + &self, + data: &[u8], + bit_len: usize, + level_mask: LevelMask, + ) -> Result, io::Error> { + let current_index = if self.is_config_proof(bit_len) { 1 } else { 2 }; + + let cursor = Cursor::new(&data[current_index..]); + let mut reader = ByteReader::endian(cursor, BigEndian); + + let level = level_mask.level() as usize; + let hashes = (0..level) + .map(|_| reader.read::()) + .collect::, _>>()?; + let depths = (0..level) + .map(|_| reader.read::()) + .collect::, _>>()?; + + let result = hashes + .into_iter() + .zip(depths) + .map(|(hash, depth)| Pruned { depth, hash }) + .collect(); + + Ok(result) + } + + /// Special case for config proof + /// This test proof is generated in the moment of voting for a slashing + /// it seems that tools generate it incorrectly and therefore doesn't have mask in it + /// so we need to hardcode it equal to 1 in this case + fn is_config_proof(&self, bit_len: usize) -> bool { + self == &CellType::PrunedBranch && bit_len == 280 + } +} diff --git a/token-core/tcx-libs/tonlib-core/src/cell/error.rs b/token-core/tcx-libs/tonlib-core/src/cell/error.rs new file mode 100644 index 00000000..7baaa361 --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/src/cell/error.rs @@ -0,0 +1,118 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum TonCellError { + #[error("Bag of cells deserialization error ({0})")] + BagOfCellsDeserializationError(String), + + #[error("Bag of cells serialization error ({0})")] + BagOfCellsSerializationError(String), + + #[error("Cell builder error ({0})")] + CellBuilderError(String), + + #[error("Cell parser error ({0})")] + CellParserError(String), + + #[error("Internal error ({0})")] + InternalError(String), + + #[error("Invalid index (Index: {idx}, reference count: {ref_count})")] + InvalidIndex { idx: usize, ref_count: usize }, + + #[error("Invalid address type (Type: {0})")] + InvalidAddressType(u8), + + #[error("Invalid cell type for exotic cell (Type: {0:?})")] + InvalidExoticCellType(Option), + + #[error("Invalid exotic cell data (({0})")] + InvalidExoticCellData(String), + + #[error("Invalid cell data ({0})")] + InvalidCellData(String), + + #[error("Invalid input error ({0})")] + InvalidInput(String), + + #[error("Invalid init code ({0})")] + InvalidInitCode(String), + + #[error( + "Non-empty reader (Remaining bits: {remaining_bits}, Remaining refs: {remaining_refs})" + )] + NonEmptyReader { + remaining_bits: usize, + remaining_refs: usize, + }, +} + +pub trait MapTonCellError +where + E: std::error::Error, +{ + fn map_boc_deserialization_error(self) -> Result; + + fn map_boc_serialization_error(self) -> Result; + + fn map_cell_builder_error(self) -> Result; + + fn map_cell_parser_error(self) -> Result; +} + +impl MapTonCellError for Result +where + E: std::error::Error, +{ + fn map_boc_serialization_error(self) -> Result { + self.map_err(|e| TonCellError::boc_serialization_error(e)) + } + + fn map_boc_deserialization_error(self) -> Result { + self.map_err(|e| TonCellError::boc_deserialization_error(e)) + } + + fn map_cell_builder_error(self) -> Result { + self.map_err(|e| TonCellError::cell_builder_error(e)) + } + + fn map_cell_parser_error(self) -> Result { + self.map_err(|e| TonCellError::cell_parser_error(e)) + } +} + +impl TonCellError { + pub fn boc_serialization_error(e: T) -> TonCellError + where + T: ToString, + { + TonCellError::BagOfCellsSerializationError(format!( + "BoC serialization error: {}", + e.to_string() + )) + } + + pub fn boc_deserialization_error(e: T) -> TonCellError + where + T: ToString, + { + TonCellError::BagOfCellsDeserializationError(format!( + "BoC deserialization error: {}", + e.to_string() + )) + } + + pub fn cell_builder_error(e: T) -> TonCellError + where + T: ToString, + { + TonCellError::CellBuilderError(format!("Cell builder error: {}", e.to_string())) + } + + pub fn cell_parser_error(e: T) -> TonCellError + where + T: ToString, + { + TonCellError::CellParserError(format!("Cell parser error: {}", e.to_string())) + } +} diff --git a/token-core/tcx-libs/tonlib-core/src/cell/level_mask.rs b/token-core/tcx-libs/tonlib-core/src/cell/level_mask.rs new file mode 100644 index 00000000..e4f7f4c3 --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/src/cell/level_mask.rs @@ -0,0 +1,48 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct LevelMask { + mask: u32, +} + +impl LevelMask { + pub fn new(new_mask: u32) -> Self { + Self { mask: new_mask } + } + + pub fn mask(&self) -> u32 { + self.mask + } + + pub fn level(&self) -> u8 { + 32 - self.mask.leading_zeros() as u8 + } + + pub fn hash_index(&self) -> usize { + self.mask.count_ones() as usize + } + + pub fn hash_count(&self) -> usize { + self.hash_index() + 1 + } + + pub fn apply(&self, level: u8) -> Self { + LevelMask { + mask: self.mask & ((1u32 << level) - 1), + } + } + + pub fn apply_or(&self, other: Self) -> Self { + LevelMask { + mask: self.mask | other.mask, + } + } + + pub fn shift_right(&self) -> Self { + LevelMask { + mask: self.mask >> 1, + } + } + + pub fn is_significant(&self, level: u8) -> bool { + level == 0 || ((self.mask >> (level - 1)) % 2 != 0) + } +} diff --git a/token-core/tcx-libs/tonlib-core/src/cell/parser.rs b/token-core/tcx-libs/tonlib-core/src/cell/parser.rs new file mode 100644 index 00000000..7b152b58 --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/src/cell/parser.rs @@ -0,0 +1,99 @@ +use std::io::Cursor; + +use bitstream_io::{BigEndian, BitRead, BitReader, Numeric}; + +use super::ArcCell; +use crate::cell::{MapTonCellError, TonCellError}; + +pub struct CellParser<'a> { + pub(crate) bit_len: usize, + pub(crate) bit_reader: BitReader, BigEndian>, + pub(crate) references: &'a [ArcCell], + next_ref: usize, +} + +impl<'a> CellParser<'a> { + pub fn new(bit_len: usize, data: &'a [u8], references: &'a [ArcCell]) -> Self { + let cursor = Cursor::new(data); + let bit_reader = BitReader::endian(cursor, BigEndian); + CellParser { + bit_len, + bit_reader, + references, + next_ref: 0, + } + } + + pub fn remaining_bits(&mut self) -> usize { + let pos = self.bit_reader.position_in_bits().unwrap_or_default() as usize; + if self.bit_len > pos { + self.bit_len - pos + } else { + 0 + } + } + + pub fn load_bit(&mut self) -> Result { + self.ensure_enough_bits(1)?; + self.bit_reader.read_bit().map_cell_parser_error() + } + + pub fn load_u8(&mut self, bit_len: usize) -> Result { + self.load_number(bit_len) + } + + pub fn load_u32(&mut self, bit_len: usize) -> Result { + self.load_number(bit_len) + } + + pub fn load_i32(&mut self, bit_len: usize) -> Result { + self.load_number(bit_len) + } + + pub fn load_u64(&mut self, bit_len: usize) -> Result { + self.load_number(bit_len) + } + + pub fn load_slice(&mut self, slice: &mut [u8]) -> Result<(), TonCellError> { + self.ensure_enough_bits(slice.len() * 8)?; + self.bit_reader.read_bytes(slice).map_cell_parser_error() + } + + pub fn ensure_empty(&mut self) -> Result<(), TonCellError> { + let remaining_bits = self.remaining_bits(); + let remaining_refs = self.references.len() - self.next_ref; + // if remaining_bits == 0 && remaining_refs == 0 { // todo: We will restore reference checking in in 0.18 + if remaining_bits == 0 { + Ok(()) + } else { + Err(TonCellError::NonEmptyReader { + remaining_bits, + remaining_refs, + }) + } + } + + pub fn skip_bits(&mut self, num_bits: usize) -> Result<(), TonCellError> { + self.ensure_enough_bits(num_bits)?; + self.bit_reader + .skip(num_bits as u32) + .map_cell_parser_error() + } + + fn load_number(&mut self, bit_len: usize) -> Result { + self.ensure_enough_bits(bit_len)?; + + self.bit_reader + .read::(bit_len as u32) + .map_cell_parser_error() + } + + fn ensure_enough_bits(&mut self, bit_len: usize) -> Result<(), TonCellError> { + if self.remaining_bits() < bit_len { + return Err(TonCellError::CellParserError( + "Not enough bits to read".to_owned(), + )); + } + Ok(()) + } +} diff --git a/token-core/tcx-libs/tonlib-core/src/cell/raw.rs b/token-core/tcx-libs/tonlib-core/src/cell/raw.rs new file mode 100644 index 00000000..0013524a --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/src/cell/raw.rs @@ -0,0 +1,188 @@ +use std::io::Cursor; + +use crate::cell::level_mask::LevelMask; +use crate::cell::{MapTonCellError, TonCellError}; +use bitstream_io::{BigEndian, ByteRead, ByteReader}; + +/// Raw representation of Cell. +/// +/// References are stored as indices in BagOfCells. +#[derive(PartialEq, Eq, Debug, Clone, Hash)] +pub(crate) struct RawCell { + pub(crate) data: Vec, + pub(crate) bit_len: usize, + pub(crate) references: Vec, + pub(crate) is_exotic: bool, + level_mask: u32, +} + +impl RawCell { + pub(crate) fn new( + data: Vec, + bit_len: usize, + references: Vec, + level_mask: u32, + is_exotic: bool, + ) -> Self { + Self { + data, + bit_len, + references, + level_mask: level_mask & 7, + is_exotic, + } + } +} + +/// Raw representation of BagOfCells. +/// +/// `cells` must be topologically sorted. +#[derive(PartialEq, Eq, Debug, Clone, Hash)] +pub(crate) struct RawBagOfCells { + pub(crate) cells: Vec, + pub(crate) roots: Vec, +} + +const GENERIC_BOC_MAGIC: u32 = 0xb5ee9c72; +const _INDEXED_BOC_MAGIC: u32 = 0x68ff65f3; +const _INDEXED_CRC32_MAGIC: u32 = 0xacc3a728; + +impl RawBagOfCells { + pub(crate) fn parse(serial: &[u8]) -> Result { + let cursor = Cursor::new(serial); + + let mut reader: ByteReader, BigEndian> = + ByteReader::endian(cursor, BigEndian); + // serialized_boc#b5ee9c72 + let magic = reader.read::().map_boc_deserialization_error()?; + + let (has_idx, has_crc32c, _has_cache_bits, size) = match magic { + GENERIC_BOC_MAGIC => { + // has_idx:(## 1) has_crc32c:(## 1) has_cache_bits:(## 1) flags:(## 2) { flags = 0 } + let header = reader.read::().map_boc_deserialization_error()?; + let has_idx = (header >> 7) & 1 == 1; + let has_crc32c = (header >> 6) & 1 == 1; + let has_cache_bits = (header >> 5) & 1 == 1; + // size:(## 3) { size <= 4 } + let size = header & 0b0000_0111; + + (has_idx, has_crc32c, has_cache_bits, size) + } + magic => { + return Err(TonCellError::boc_deserialization_error(format!( + "Unsupported cell magic number: {:#}", + magic + ))); + } + }; + // off_bytes:(## 8) { off_bytes <= 8 } + let off_bytes = reader.read::().map_boc_deserialization_error()?; + //cells:(##(size * 8)) + let cells = read_var_size(&mut reader, size)?; + // roots:(##(size * 8)) { roots >= 1 } + let roots = read_var_size(&mut reader, size)?; + // absent:(##(size * 8)) { roots + absent <= cells } + let _absent = read_var_size(&mut reader, size)?; + // tot_cells_size:(##(off_bytes * 8)) + let _tot_cells_size = read_var_size(&mut reader, off_bytes)?; + // root_list:(roots * ##(size * 8)) + let mut root_list = vec![]; + for _ in 0..roots { + root_list.push(read_var_size(&mut reader, size)?) + } + // index:has_idx?(cells * ##(off_bytes * 8)) + let mut index = vec![]; + if has_idx { + for _ in 0..cells { + index.push(read_var_size(&mut reader, off_bytes)?) + } + } + // cell_data:(tot_cells_size * [ uint8 ]) + let mut cell_vec = Vec::with_capacity(cells); + + for _ in 0..cells { + let cell = read_cell(&mut reader, size)?; + cell_vec.push(cell); + } + // crc32c:has_crc32c?uint32 + let _crc32c = if has_crc32c { + reader.read::().map_boc_deserialization_error()? + } else { + 0 + }; + // TODO: Check crc32 + + Ok(RawBagOfCells { + cells: cell_vec, + roots: root_list, + }) + } +} + +fn read_cell( + reader: &mut ByteReader, BigEndian>, + size: u8, +) -> Result { + let d1 = reader.read::().map_boc_deserialization_error()?; + let d2 = reader.read::().map_boc_deserialization_error()?; + + let ref_num = d1 & 0b111; + let is_exotic = (d1 & 0b1000) != 0; + let has_hashes = (d1 & 0b10000) != 0; + let level_mask = (d1 >> 5) as u32; + let data_size = ((d2 >> 1) + (d2 & 1)).into(); + let full_bytes = (d2 & 0x01) == 0; + + if has_hashes { + let hash_count = LevelMask::new(level_mask).hash_count(); + let skip_size = hash_count * (32 + 2); + + // TODO: check depth and hashes + reader + .skip(skip_size as u32) + .map_boc_deserialization_error()?; + } + + let mut data = reader + .read_to_vec(data_size) + .map_boc_deserialization_error()?; + + let data_len = data.len(); + let padding_len = if data_len > 0 && !full_bytes { + // Fix last byte, + // see https://github.com/toncenter/tonweb/blob/c2d5d0fc23d2aec55a0412940ce6e580344a288c/src/boc/BitString.js#L302 + let num_zeros = data[data_len - 1].trailing_zeros(); + if num_zeros >= 8 { + return Err(TonCellError::boc_deserialization_error( + "Last byte of binary must not be zero if full_byte flag is not set", + )); + } + data[data_len - 1] &= !(1 << num_zeros); + num_zeros + 1 + } else { + 0 + }; + let bit_len = data.len() * 8 - padding_len as usize; + let mut references: Vec = Vec::new(); + for _ in 0..ref_num { + references.push(read_var_size(reader, size)?); + } + let cell = RawCell::new(data, bit_len, references, level_mask, is_exotic); + Ok(cell) +} + +fn read_var_size( + reader: &mut ByteReader, BigEndian>, + n: u8, +) -> Result { + let bytes = reader + .read_to_vec(n.into()) + .map_boc_deserialization_error()?; + + let mut result = 0; + for &byte in &bytes { + result <<= 8; + result |= usize::from(byte); + } + Ok(result) +} diff --git a/token-core/tcx-libs/tonlib-core/src/cell/state_init.rs b/token-core/tcx-libs/tonlib-core/src/cell/state_init.rs new file mode 100644 index 00000000..c53754e0 --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/src/cell/state_init.rs @@ -0,0 +1,58 @@ +use super::ArcCell; +use crate::cell::{Cell, CellBuilder, TonCellError}; +use crate::TonHash; + +pub struct StateInitBuilder { + code: Option, + data: Option, + split_depth: bool, + tick_tock: bool, + library: bool, +} +pub struct StateInit { + pub code: Option, + pub data: Option, +} + +impl StateInitBuilder { + pub fn new(code: &ArcCell, data: &ArcCell) -> StateInitBuilder { + StateInitBuilder { + code: Some(code.clone()), + data: Some(data.clone()), + split_depth: false, + tick_tock: false, + library: false, + } + } + + pub fn with_library(&mut self, library: bool) -> &mut Self { + self.library = library; + self + } + + pub fn build(&self) -> Result { + let mut builder = CellBuilder::new(); + builder + .store_bit(self.split_depth)? //Split depth + .store_bit(self.tick_tock)? //Tick tock + .store_bit(self.code.is_some())? //Code + .store_bit(self.data.is_some())? //Data + .store_bit(self.library)?; //Library + if let Some(code) = &self.code { + builder.store_reference(code)?; + } + if let Some(data) = &self.data { + builder.store_reference(data)?; + } + builder.build() + } +} + +impl StateInit { + pub fn create_account_id(code: &ArcCell, data: &ArcCell) -> Result { + Ok(StateInitBuilder::new(code, data) + .with_library(false) + .build()? + .cell_hash()) + } +} diff --git a/token-core/tcx-libs/tonlib-core/src/lib.rs b/token-core/tcx-libs/tonlib-core/src/lib.rs new file mode 100644 index 00000000..1cf4e2ad --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/src/lib.rs @@ -0,0 +1,5 @@ +pub mod cell; +pub mod types; +pub mod wallet; + +pub use crate::types::{TonAddress, TonAddressParseError, TonHash}; diff --git a/token-core/tcx-libs/tonlib-core/src/types.rs b/token-core/tcx-libs/tonlib-core/src/types.rs new file mode 100644 index 00000000..29f2f7f2 --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/src/types.rs @@ -0,0 +1,14 @@ +mod address; +mod error; + +pub use address::*; +pub use error::*; + +pub const TON_HASH_BYTES: usize = 32; +pub const ZERO_HASH: TonHash = [0; 32]; +pub type TonHash = [u8; TON_HASH_BYTES]; + +pub const DEFAULT_CELL_HASH: TonHash = [ + 150, 162, 150, 210, 36, 242, 133, 198, 123, 238, 147, 195, 15, 138, 48, 145, 87, 240, 218, 163, + 93, 197, 184, 126, 65, 11, 120, 99, 10, 9, 207, 199, +]; diff --git a/token-core/tcx-libs/tonlib-core/src/types/address.rs b/token-core/tcx-libs/tonlib-core/src/types/address.rs new file mode 100644 index 00000000..5260d04d --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/src/types/address.rs @@ -0,0 +1,486 @@ +use std::fmt::{Debug, Display, Formatter}; +use std::str::FromStr; + +use base64::engine::general_purpose::{STANDARD_NO_PAD, URL_SAFE_NO_PAD}; +use base64::Engine; +use crc::Crc; +use lazy_static::lazy_static; +use serde::de::{Error, Visitor}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use super::{TonAddressParseError, TonHash, TON_HASH_BYTES}; + +lazy_static! { + pub static ref CRC_16_XMODEM: Crc = Crc::::new(&crc::CRC_16_XMODEM); +} + +#[derive(PartialEq, Eq, Clone, Hash)] +pub struct TonAddress { + pub workchain: i32, + pub hash_part: TonHash, +} + +impl TonAddress { + pub const NULL: TonAddress = TonAddress { + workchain: 0, + hash_part: [0; TON_HASH_BYTES], + }; + + pub fn new(workchain: i32, hash_part: &TonHash) -> TonAddress { + TonAddress { + workchain, + hash_part: *hash_part, + } + } + + pub fn null() -> TonAddress { + TonAddress::NULL.clone() + } + + pub fn from_hex_str(s: &str) -> Result { + let parts: Vec<&str> = s.split(':').collect(); + + if parts.len() != 2 { + return Err(TonAddressParseError::new( + s, + "Invalid hex address string: wrong address format", + )); + } + + let maybe_wc = parts[0].parse::(); + let wc = match maybe_wc { + Ok(wc) => wc, + Err(_) => { + return Err(TonAddressParseError::new( + s, + "Invalid hex address string: parse int error", + )) + } + }; + + let maybe_decoded_hash_part = hex::decode(parts[1]); + let decoded_hash_part = match maybe_decoded_hash_part { + Ok(decoded_hash_part) => decoded_hash_part, + Err(_) => { + return Err(TonAddressParseError::new( + s, + "Invalid hex address string: base64 decode error", + )) + } + }; + + let maybe_hash_part = decoded_hash_part.as_slice().try_into(); + let hash_part = match maybe_hash_part { + Ok(hash_part) => hash_part, + Err(_) => { + return Err(TonAddressParseError::new( + s, + "Invalid hex address string: unexpected error", + )) + } + }; + + let addr = TonAddress::new(wc, &hash_part); + Ok(addr) + } + + pub fn from_base64_url(s: &str) -> Result { + Ok(Self::from_base64_url_flags(s)?.0) + } + + /// Parses url-safe base64 representation of an address + /// + /// # Returns + /// the address, non-bounceable flag, non-production flag. + pub fn from_base64_url_flags( + s: &str, + ) -> Result<(TonAddress, bool, bool), TonAddressParseError> { + if s.len() != 48 { + return Err(TonAddressParseError::new( + s, + "Invalid base64url address: Wrong length", + )); + } + let maybe_bytes = URL_SAFE_NO_PAD.decode(s); + let bytes = match maybe_bytes { + Ok(bytes) => bytes, + Err(_) => { + return Err(TonAddressParseError::new( + s, + "Invalid base64url address: Base64 decode error", + )) + } + }; + let maybe_slice = bytes.as_slice().try_into(); + let slice = match maybe_slice { + Ok(slice) => slice, + Err(_) => { + return Err(TonAddressParseError::new( + s, + "Invalid base64url address: Unexpected error", + )) + } + }; + + Self::from_base64_src(slice, s) + } + + pub fn from_base64_std(s: &str) -> Result { + Ok(Self::from_base64_std_flags(s)?.0) + } + + /// Parses standard base64 representation of an address + /// + /// # Returns + /// the address, non-bounceable flag, non-production flag. + pub fn from_base64_std_flags( + s: &str, + ) -> Result<(TonAddress, bool, bool), TonAddressParseError> { + if s.len() != 48 { + return Err(TonAddressParseError::new( + s, + "Invalid base64std address: Invalid length", + )); + } + + let maybe_vec = STANDARD_NO_PAD.decode(s); + let vec = match maybe_vec { + Ok(bytes) => bytes, + Err(_) => { + return Err(TonAddressParseError::new( + s, + "Invalid base64std address: Base64 decode error", + )) + } + }; + let maybe_bytes = vec.as_slice().try_into(); + let bytes = match maybe_bytes { + Ok(b) => b, + Err(_) => { + return Err(TonAddressParseError::new( + s, + "Invalid base64std: Unexpected error", + )) + } + }; + + Self::from_base64_src(bytes, s) + } + + /// Parses decoded base64 representation of an address + /// + /// # Returns + /// the address, non-bounceable flag, non-production flag. + fn from_base64_src( + bytes: &[u8; 36], + src: &str, + ) -> Result<(TonAddress, bool, bool), TonAddressParseError> { + let (non_production, non_bounceable) = match bytes[0] { + 0x11 => (false, false), + 0x51 => (false, true), + 0x91 => (true, false), + 0xD1 => (true, true), + _ => { + return Err(TonAddressParseError::new( + src, + "Invalid base64src address: Wrong tag byte", + )) + } + }; + let workchain = bytes[1] as i8 as i32; + let calc_crc = CRC_16_XMODEM.checksum(&bytes[0..34]); + let addr_crc = ((bytes[34] as u16) << 8) | bytes[35] as u16; + if calc_crc != addr_crc { + return Err(TonAddressParseError::new( + src, + "Invalid base64src address: CRC mismatch", + )); + } + let mut hash_part = [0_u8; 32]; + hash_part.clone_from_slice(&bytes[2..34]); + let addr = TonAddress { + workchain, + hash_part, + }; + Ok((addr, non_bounceable, non_production)) + } + + pub fn to_hex(&self) -> String { + format!("{}:{}", self.workchain, hex::encode(self.hash_part)) + } + + pub fn to_base64_url(&self) -> String { + self.to_base64_url_flags(false, false) + } + + pub fn to_base64_url_flags(&self, non_bounceable: bool, non_production: bool) -> String { + let mut buf: [u8; 36] = [0; 36]; + self.to_base64_src(&mut buf, non_bounceable, non_production); + URL_SAFE_NO_PAD.encode(buf) + } + + pub fn to_base64_std(&self) -> String { + self.to_base64_std_flags(false, false) + } + + pub fn to_base64_std_flags(&self, non_bounceable: bool, non_production: bool) -> String { + let mut buf: [u8; 36] = [0; 36]; + self.to_base64_src(&mut buf, non_bounceable, non_production); + STANDARD_NO_PAD.encode(buf) + } + + fn to_base64_src(&self, bytes: &mut [u8; 36], non_bounceable: bool, non_production: bool) { + let tag: u8 = match (non_production, non_bounceable) { + (false, false) => 0x11, + (false, true) => 0x51, + (true, false) => 0x91, + (true, true) => 0xD1, + }; + bytes[0] = tag; + bytes[1] = (self.workchain & 0xff) as u8; + bytes[2..34].clone_from_slice(&self.hash_part); + let crc = CRC_16_XMODEM.checksum(&bytes[0..34]); + bytes[34] = ((crc >> 8) & 0xff) as u8; + bytes[35] = (crc & 0xff) as u8; + } +} + +impl Display for TonAddress { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_base64_url().as_str()) + } +} + +impl Debug for TonAddress { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_base64_url().as_str()) + } +} + +impl FromStr for TonAddress { + type Err = TonAddressParseError; + + fn from_str(s: &str) -> Result { + if s.len() == 48 { + // Some form of base64 address, check which one + if s.contains('-') || s.contains('_') { + TonAddress::from_base64_url(s) + } else { + TonAddress::from_base64_std(s) + } + } else { + TonAddress::from_hex_str(s) + } + } +} + +impl TryFrom for TonAddress { + type Error = TonAddressParseError; + + fn try_from(value: String) -> Result { + Self::from_str(value.as_str()) + } +} + +impl Serialize for TonAddress { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.to_base64_url().as_str()) + } +} + +struct TonAddressVisitor; + +impl<'de> Visitor<'de> for TonAddressVisitor { + type Value = TonAddress; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str("an string representing TON address in Hex or Base64 format") + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + v.parse().map_err(E::custom) + } +} + +impl<'de> Deserialize<'de> for TonAddress { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(TonAddressVisitor) + } +} + +#[cfg(test)] +mod tests { + + use serde_json::Value; + + use super::TonAddressParseError; + use crate::{TonAddress, TonHash}; + + #[test] + fn format_works() -> Result<(), TonAddressParseError> { + let bytes: TonHash = + hex::decode("e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76") + .unwrap() + .as_slice() + .try_into() + .unwrap(); + let addr = TonAddress::new(0, &bytes); + assert_eq!( + addr.to_hex(), + "0:e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76" + ); + assert_eq!( + addr.to_base64_url(), + "EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR" + ); + assert_eq!( + addr.to_base64_std(), + "EQDk2VTvn04SUKJrW7rXahzdF8/Qi6utb0wj43InCu9vdjrR" + ); + Ok(()) + } + + #[test] + fn parse_format_works() -> Result<(), TonAddressParseError> { + let bytes: TonHash = + hex::decode("e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76") + .unwrap() + .as_slice() + .try_into() + .unwrap(); + let addr = TonAddress::new(0, &bytes); + assert_eq!( + TonAddress::from_hex_str( + "0:e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76" + )?, + addr + ); + assert_eq!( + TonAddress::from_base64_url("EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR")?, + addr + ); + assert_eq!( + TonAddress::from_base64_std("EQDk2VTvn04SUKJrW7rXahzdF8/Qi6utb0wj43InCu9vdjrR")?, + addr + ); + Ok(()) + } + + #[test] + fn parse_works() -> Result<(), TonAddressParseError> { + let bytes: TonHash = + hex::decode("e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76") + .unwrap() + .as_slice() + .try_into() + .unwrap(); + let addr = TonAddress::new(0, &bytes); + assert_eq!( + "0:e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76" + .parse::()?, + addr + ); + assert_eq!( + "EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR".parse::()?, + addr + ); + assert_eq!( + "EQDk2VTvn04SUKJrW7rXahzdF8/Qi6utb0wj43InCu9vdjrR".parse::()?, + addr + ); + Ok(()) + } + + #[test] + fn try_from_works() -> Result<(), TonAddressParseError> { + let bytes: TonHash = + hex::decode("e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76") + .unwrap() + .as_slice() + .try_into() + .unwrap(); + let addr = TonAddress::new(0, &bytes); + let res: TonAddress = "EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR" + .to_string() + .try_into()?; + assert_eq!(res, addr); + Ok(()) + } + + #[test] + fn parse_verifies_crc() -> Result<(), TonAddressParseError> { + let res = "EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjra".parse::(); + assert!(res.is_err()); + Ok(()) + } + + #[test] + fn serialization_works() -> Result<(), TonAddressParseError> { + let expected = "\"EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR\""; + + let res = "EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR".parse::()?; + let serial = serde_json::to_string(&res).unwrap(); + println!("{}", serial); + assert_eq!(serial.as_str(), expected); + + let res = "0:e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76" + .parse::()?; + let serial = serde_json::to_string(&res).unwrap(); + println!("{}", serial); + assert_eq!(serial.as_str(), expected); + + let res = "EQDk2VTvn04SUKJrW7rXahzdF8/Qi6utb0wj43InCu9vdjrR".parse::()?; + let serial = serde_json::to_string(&res).unwrap(); + println!("{}", serial); + assert_eq!(serial.as_str(), expected); + + Ok(()) + } + + #[test] + fn deserialization_works() -> Result<(), TonAddressParseError> { + let address = "EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR"; + let a = format!("\"{}\"", address); + let deserial: TonAddress = serde_json::from_str(a.as_str()).unwrap(); + let expected = address.parse()?; + println!("{}", deserial); + assert_eq!(deserial, expected); + + let address = "EQDk2VTvn04SUKJrW7rXahzdF8/Qi6utb0wj43InCu9vdjrR"; + let a = format!("\"{}\"", address); + let deserial: TonAddress = serde_json::from_str(a.as_str()).unwrap(); + let expected = address.parse()?; + println!("{}", deserial); + assert_eq!(deserial, expected); + + let address = "0:e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76"; + let a = format!("\"{}\"", address); + let deserial: TonAddress = serde_json::from_str(a.as_str()).unwrap(); + let expected = address.parse()?; + println!("{}", deserial); + assert_eq!(deserial, expected); + + let address = + String::from("0:e4d954ef9f4e1250a26b5bbad76a1cdd17cfd08babad6f4c23e372270aef6f76"); + let deserial: TonAddress = serde_json::from_value(Value::String(address.clone())).unwrap(); + let expected = address.clone().parse()?; + println!("{}", deserial); + assert_eq!(deserial, expected); + + let address = "124"; + let a = format!("\"{}\"", address); + let deserial: serde_json::Result = serde_json::from_str(a.as_str()); + assert!(deserial.is_err()); + + Ok(()) + } +} diff --git a/token-core/tcx-libs/tonlib-core/src/types/error.rs b/token-core/tcx-libs/tonlib-core/src/types/error.rs new file mode 100644 index 00000000..17b50dc0 --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/src/types/error.rs @@ -0,0 +1,17 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +#[error("Invalid address (Address: {address}, message: {message})")] +pub struct TonAddressParseError { + address: String, + message: String, +} + +impl TonAddressParseError { + pub fn new(address: A, message: M) -> TonAddressParseError { + TonAddressParseError { + address: address.to_string(), + message: message.to_string(), + } + } +} diff --git a/token-core/tcx-libs/tonlib-core/src/wallet.rs b/token-core/tcx-libs/tonlib-core/src/wallet.rs new file mode 100644 index 00000000..31a46022 --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/src/wallet.rs @@ -0,0 +1,140 @@ +mod types; + +use std::sync::Arc; + +pub use types::*; + +use crate::cell::{ArcCell, BagOfCells, Cell, TonCellError}; + +pub const DEFAULT_WALLET_ID_V5R1: i32 = 0x7FFFFF11; +pub const DEFAULT_WALLET_ID: i32 = 0x29a9a317; + +pub const WALLET_V1R1_CODE: &str = "te6cckEBAQEARAAAhP8AIN2k8mCBAgDXGCDXCx/tRNDTH9P/0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVEH98Ik="; +pub const WALLET_V1R2_CODE: &str = "te6cckEBAQEAUwAAov8AIN0gggFMl7qXMO1E0NcLH+Ck8mCBAgDXGCDXCx/tRNDTH9P/0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVNDieG8="; +pub const WALLET_V1R3_CODE: &str = "te6cckEBAQEAXwAAuv8AIN0gggFMl7ohggEznLqxnHGw7UTQ0x/XC//jBOCk8mCBAgDXGCDXCx/tRNDTH9P/0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVLW4bkI="; +pub const WALLET_V2R1_CODE: &str = "te6cckEBAQEAVwAAqv8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x8B+CO78mPtRNDTH9P/0VExuvKhA/kBVBBC+RDyovgAApMg10qW0wfUAvsA6NGkyMsfy//J7VShNwu2"; +pub const WALLET_V2R2_CODE: &str = "te6cckEBAQEAYwAAwv8AIN0gggFMl7ohggEznLqxnHGw7UTQ0x/XC//jBOCk8mCDCNcYINMf0x8B+CO78mPtRNDTH9P/0VExuvKhA/kBVBBC+RDyovgAApMg10qW0wfUAvsA6NGkyMsfy//J7VQETNeh"; +pub const WALLET_V3R1_CODE: &str = "te6cckEBAQEAYgAAwP8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVD++buA="; +pub const WALLET_V3R2_CODE: &str = "te6cckEBAQEAcQAA3v8AIN0gggFMl7ohggEznLqxn3Gw7UTQ0x/THzHXC//jBOCk8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVBC9ba0="; +pub const WALLET_V4R1_CODE: &str = "te6cckECFQEAAvUAART/APSkE/S88sgLAQIBIAIDAgFIBAUE+PKDCNcYINMf0x/THwL4I7vyY+1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8REhMUA+7QAdDTAwFxsJFb4CHXScEgkVvgAdMfIYIQcGx1Z70ighBibG5jvbAighBkc3RyvbCSXwPgAvpAMCD6RAHIygfL/8nQ7UTQgQFA1yH0BDBcgQEI9ApvoTGzkl8F4ATTP8glghBwbHVnupEx4w0kghBibG5juuMABAYHCAIBIAkKAFAB+gD0BDCCEHBsdWeDHrFwgBhQBcsFJ88WUAP6AvQAEstpyx9SEMs/AFL4J28ighBibG5jgx6xcIAYUAXLBSfPFiT6AhTLahPLH1Iwyz8B+gL0AACSghBkc3Ryuo41BIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UghBkc3Rygx6xcIAYUATLBVjPFiL6AhLLassfyz+UEDRfBOLJgED7AAIBIAsMAFm9JCtvaiaECAoGuQ+gIYRw1AgIR6STfSmRDOaQPp/5g3gSgBt4EBSJhxWfMYQCAVgNDgARuMl+1E0NcLH4AD2ynftRNCBAUDXIfQEMALIygfL/8nQAYEBCPQKb6ExgAgEgDxAAGa3OdqJoQCBrkOuF/8AAGa8d9qJoQBBrkOuFj8AAbtIH+gDU1CL5AAXIygcVy//J0Hd0gBjIywXLAiLPFlAF+gIUy2sSzMzJcfsAyEAUgQEI9FHypwIAbIEBCNcYyFQgJYEBCPRR8qeCEG5vdGVwdIAYyMsFywJQBM8WghAF9eEA+gITy2oSyx/JcfsAAgBygQEI1xgwUgKBAQj0WfKn+CWCEGRzdHJwdIAYyMsFywJQBc8WghAF9eEA+gIUy2oTyx8Syz/Jc/sAAAr0AMntVEap808="; +pub const WALLET_V4R2_CODE: &str = "te6cckECFAEAAtQAART/APSkE/S88sgLAQIBIAIDAgFIBAUE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8QERITAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNBgcCASAICQB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+gIZ9ADLaRfLH1Jgyz8gyYBA+wAGAIpQBIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UAXKwjiOCEGRzdHKDHrFwgBhQBcsFUAPPFiP6AhPLassfyz/JgED7AJJfA+ICASAKCwBZvSQrb2omhAgKBrkPoCGEcNQICEekk30pkQzmkD6f+YN4EoAbeBAUiYcVnzGEAgFYDA0AEbjJftRNDXCx+AA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIA4PABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AAG7SB/oA1NQi+QAFyMoHFcv/ydB3dIAYyMsFywIizxZQBfoCFMtrEszMyXP7AMhAFIEBCPRR8qcCAHCBAQjXGPoA0z/IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBsgQEI1xj6ANM/MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAAr0AMntVGliJeU="; +pub const WALLET_V5R1_CODE: &str = "te6ccgECFAEAAoEAART/APSkE/S88sgLAQIBIAIDAgFIBAUBAvIOAtzQINdJwSCRW49jINcLHyCCEGV4dG69IYIQc2ludL2wkl8D4IIQZXh0brqOtIAg1yEB0HTXIfpAMPpE+Cj6RDBYvZFb4O1E0IEBQdch9AWDB/QOb6ExkTDhgEDXIXB/2zzgMSDXSYECgLmRMOBw4hAPAgEgBgcCASAICQAZvl8PaiaECAoOuQ+gLAIBbgoLAgFIDA0AGa3OdqJoQCDrkOuF/8AAGa8d9qJoQBDrkOuFj8AAF7Ml+1E0HHXIdcLH4AARsmL7UTQ1woAgAR4g1wsfghBzaWduuvLgin8PAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYEAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKERITAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNA="; +pub const HIGHLOAD_V1R1_CODE: &str = "te6ccgEBBgEAhgABFP8A9KQT9KDyyAsBAgEgAgMCAUgEBQC88oMI1xgg0x/TH9Mf+CMTu/Jj7UTQ0x/TH9P/0VEyuvKhUUS68qIE+QFUEFX5EPKj9ATR+AB/jhghgBD0eG+hb6EgmALTB9QwAfsAkTLiAbPmWwGkyMsfyx/L/8ntVAAE0DAAEaCZL9qJoa4WPw=="; +pub const HIGHLOAD_V1R2_CODE: &str = "te6ccgEBCAEAmQABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQC88oMI1xgg0x/TH9Mf+CMTu/Jj7UTQ0x/TH9P/0VEyuvKhUUS68qIE+QFUEFX5EPKj9ATR+AB/jhghgBD0eG+hb6EgmALTB9QwAfsAkTLiAbPmWwGkyMsfyx/L/8ntVAAE0DACAUgGBwAXuznO1E0NM/MdcL/4ABG4yX7UTQ1wsfg="; +pub const HIGHLOAD_V2_CODE: &str = "te6ccgEBCQEA5QABFP8A9KQT9LzyyAsBAgEgAgcCAUgDBAAE0DACASAFBgAXvZznaiaGmvmOuF/8AEG+X5dqJoaY+Y6Z/p/5j6AmipEEAgegc30JjJLb/JXdHxQB6vKDCNcYINMf0z/4I6ofUyC58mPtRNDTH9M/0//0BNFTYIBA9A5voTHyYFFzuvKiB/kBVBCH+RDyowL0BNH4AH+OFiGAEPR4b6UgmALTB9QwAfsAkTLiAbPmW4MlochANIBA9EOK5jEByMsfE8s/y//0AMntVAgANCCAQPSWb6VsEiCUMFMDud4gkzM2AZJsIeKz"; +pub const HIGHLOAD_V2R1_CODE: &str = "te6ccgEBCQEA6QABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQHu8oMI1xgg0x/TP/gjqh9TILnyY+1E0NMf0z/T//QE0VNggED0Dm+hMfJgUXO68qIH+QFUEIf5EPKjAvQE0fgAf44YIYAQ9HhvoW+hIJgC0wfUMAH7AJEy4gGz5luDJaHIQDSAQPRDiuYxyBLLHxPLP8v/9ADJ7VQIAATQMAIBIAYHABe9nOdqJoaa+Y64X/wAQb5fl2omhpj5jpn+n/mPoCaKkQQCB6BzfQmMktv8ld0fFAA4IIBA9JZvoW+hMlEQlDBTA7neIJMzNgGSMjDisw=="; +pub const HIGHLOAD_V2R2_CODE: &str = "te6ccgEBCQEA6QABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQHu8oMI1xgg0x/TP/gjqh9TILnyY+1E0NMf0z/T//QE0VNggED0Dm+hMfJgUXO68qIH+QFUEIf5EPKjAvQE0fgAf44YIYAQ9HhvoW+hIJgC0wfUMAH7AJEy4gGz5luDJaHIQDSAQPRDiuYxyBLLHxPLP8v/9ADJ7VQIAATQMAIBIAYHABe9nOdqJoaa+Y64X/wAQb5fl2omhpj5jpn+n/mPoCaKkQQCB6BzfQmMktv8ld0fFAA4IIBA9JZvoW+hMlEQlDBTA7neIJMzNgGSMjDisw=="; + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum WalletVersion { + V1R1, + V1R2, + V1R3, + V2R1, + V2R2, + V3R1, + V3R2, + V4R1, + V4R2, + V5R1, + HighloadV1R1, + HighloadV1R2, + HighloadV2, + HighloadV2R1, + HighloadV2R2, +} + +impl WalletVersion { + pub fn code(&self) -> Result { + let code = match self { + WalletVersion::V1R1 => WALLET_V1R1_CODE, + WalletVersion::V1R2 => &WALLET_V1R2_CODE, + WalletVersion::V1R3 => &WALLET_V1R3_CODE, + WalletVersion::V2R1 => &WALLET_V2R1_CODE, + WalletVersion::V2R2 => &WALLET_V2R2_CODE, + WalletVersion::V3R1 => &WALLET_V3R1_CODE, + WalletVersion::V3R2 => &WALLET_V3R2_CODE, + WalletVersion::V4R1 => &WALLET_V4R1_CODE, + WalletVersion::V4R2 => &WALLET_V4R2_CODE, + WalletVersion::V5R1 => &WALLET_V5R1_CODE, + WalletVersion::HighloadV1R1 => &HIGHLOAD_V1R1_CODE, + WalletVersion::HighloadV1R2 => &HIGHLOAD_V1R2_CODE, + WalletVersion::HighloadV2 => &HIGHLOAD_V2_CODE, + WalletVersion::HighloadV2R1 => &HIGHLOAD_V2R1_CODE, + WalletVersion::HighloadV2R2 => &HIGHLOAD_V2R2_CODE, + }; + let bag_of_cells = BagOfCells::parse_base64(code).expect("ton_parse_base64_error"); + let cell = bag_of_cells.single_root()?; + Ok(cell.clone()) + } + + pub fn initial_data(&self, key: &[u8], wallet_id: i32) -> Result { + let mut public_key: [u8; 32] = [0; 32]; + public_key.copy_from_slice(&key.to_vec()); + let data_cell: Cell = match &self { + WalletVersion::V1R1 + | WalletVersion::V1R2 + | WalletVersion::V1R3 + | WalletVersion::V2R1 + | WalletVersion::V2R2 => WalletDataV1V2 { + seqno: 0, + public_key, + } + .try_into()?, + WalletVersion::V3R1 | WalletVersion::V3R2 => WalletDataV3 { + seqno: 0, + wallet_id, + public_key, + } + .try_into()?, + WalletVersion::V4R1 | WalletVersion::V4R2 => WalletDataV4 { + seqno: 0, + wallet_id, + public_key, + } + .try_into()?, + WalletVersion::V5R1 => WalletDataV5 { + signature_allowed: true, + seqno: 0, + wallet_id, + public_key, + } + .try_into()?, + WalletVersion::HighloadV2R2 => WalletDataHighloadV2R2 { + wallet_id, + last_cleaned_time: 0, + public_key, + } + .try_into()?, + WalletVersion::HighloadV1R1 + | WalletVersion::HighloadV1R2 + | WalletVersion::HighloadV2 + | WalletVersion::HighloadV2R1 => { + return Err(TonCellError::InternalError( + "No generation for this wallet version".to_string(), + )); + } + }; + + Ok(Arc::new(data_cell)) + } + + pub fn from_code(code: &str) -> Result { + let wallet_version = match code { + WALLET_V1R3_CODE => WalletVersion::V1R3, + WALLET_V2R1_CODE => WalletVersion::V2R1, + WALLET_V2R2_CODE => WalletVersion::V2R2, + WALLET_V3R1_CODE => WalletVersion::V3R1, + WALLET_V3R2_CODE => WalletVersion::V3R2, + WALLET_V4R1_CODE => WalletVersion::V4R1, + WALLET_V4R2_CODE => WalletVersion::V4R2, + WALLET_V5R1_CODE => WalletVersion::V5R1, + HIGHLOAD_V1R1_CODE => WalletVersion::HighloadV1R1, + HIGHLOAD_V1R2_CODE => WalletVersion::HighloadV1R2, + HIGHLOAD_V2R2_CODE => WalletVersion::HighloadV2R2, + _ => return Err(TonCellError::InvalidInitCode(code.to_string())), + }; + + Ok(wallet_version) + } +} diff --git a/token-core/tcx-libs/tonlib-core/src/wallet/types.rs b/token-core/tcx-libs/tonlib-core/src/wallet/types.rs new file mode 100644 index 00000000..e1042919 --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/src/wallet/types.rs @@ -0,0 +1,190 @@ +use crate::cell::{Cell, CellBuilder, TonCellError}; +use crate::TonHash; + +/// WalletVersion::V1R1 | WalletVersion::V1R2 | WalletVersion::V1R3 | WalletVersion::V2R1 | WalletVersion::V2R2 +pub struct WalletDataV1V2 { + pub seqno: u32, + pub public_key: TonHash, +} + +impl TryFrom for WalletDataV1V2 { + type Error = TonCellError; + + fn try_from(value: Cell) -> Result { + let mut parser = value.parser(); + let seqno = parser.load_u32(32)?; + let mut public_key = [0u8; 32]; + parser.load_slice(&mut public_key)?; + Ok(Self { seqno, public_key }) + } +} + +impl TryFrom for Cell { + type Error = TonCellError; + + fn try_from(value: WalletDataV1V2) -> Result { + CellBuilder::new() + .store_u32(32, value.seqno)? + .store_slice(&value.public_key)? + .build() + } +} + +/// WalletVersion::V3R1 | WalletVersion::V3R2 +pub struct WalletDataV3 { + pub seqno: u32, + pub wallet_id: i32, + pub public_key: TonHash, +} + +impl TryFrom for WalletDataV3 { + type Error = TonCellError; + + fn try_from(value: Cell) -> Result { + let mut parser = value.parser(); + let seqno = parser.load_u32(32)?; + let wallet_id = parser.load_i32(32)?; + let mut public_key = [0u8; 32]; + parser.load_slice(&mut public_key)?; + Ok(Self { + seqno, + wallet_id, + public_key, + }) + } +} + +impl TryFrom for Cell { + type Error = TonCellError; + + fn try_from(value: WalletDataV3) -> Result { + CellBuilder::new() + .store_u32(32, value.seqno)? + .store_i32(32, value.wallet_id)? + .store_slice(&value.public_key)? + .build() + } +} + +/// WalletVersion::V4R1 | WalletVersion::V4R2 +pub struct WalletDataV4 { + pub seqno: u32, + pub wallet_id: i32, + pub public_key: TonHash, +} + +impl TryFrom for WalletDataV4 { + type Error = TonCellError; + + fn try_from(value: Cell) -> Result { + let mut parser = value.parser(); + let seqno = parser.load_u32(32)?; + let wallet_id = parser.load_i32(32)?; + let mut public_key = [0u8; 32]; + parser.load_slice(&mut public_key)?; + // TODO: handle plugin dict + Ok(Self { + seqno, + wallet_id, + public_key, + }) + } +} + +impl TryFrom for Cell { + type Error = TonCellError; + + fn try_from(value: WalletDataV4) -> Result { + CellBuilder::new() + .store_u32(32, value.seqno)? + .store_i32(32, value.wallet_id)? + .store_slice(&value.public_key)? + // empty plugin dict + .store_bit(false)? + .build() + } +} + +/// WalletVersion::V5R1 +pub struct WalletDataV5 { + pub signature_allowed: bool, + pub seqno: u32, + pub wallet_id: i32, + pub public_key: TonHash, +} + +impl TryFrom for WalletDataV5 { + type Error = TonCellError; + + fn try_from(value: Cell) -> Result { + let mut parser = value.parser(); + let signature_allowed = parser.load_bit()?; + let seqno = parser.load_u32(32)?; + let wallet_id = parser.load_i32(32)?; + let mut public_key = [0u8; 32]; + parser.load_slice(&mut public_key)?; + // TODO: handle plugin dict + let _has_extensions = parser.load_bit()?; + Ok(Self { + signature_allowed, + seqno, + wallet_id, + public_key, + }) + } +} + +impl TryFrom for Cell { + type Error = TonCellError; + + fn try_from(value: WalletDataV5) -> Result { + CellBuilder::new() + // .store_bit(value.preload_bit)? + .store_bit(true)? // sign-allowed + .store_u32(32, value.seqno)? + .store_i32(32, value.wallet_id)? + .store_slice(&value.public_key)? + .store_bit(false)? + .build() + } +} + +/// WalletVersion::HighloadV2R2 +pub struct WalletDataHighloadV2R2 { + pub wallet_id: i32, + pub last_cleaned_time: u64, + pub public_key: TonHash, +} + +impl TryFrom for WalletDataHighloadV2R2 { + type Error = TonCellError; + + fn try_from(value: Cell) -> Result { + let mut parser = value.parser(); + let wallet_id = parser.load_i32(32)?; + let last_cleaned_time = parser.load_u64(64)?; + let mut public_key = [0u8; 32]; + parser.load_slice(&mut public_key)?; + // TODO: handle queries dict + Ok(Self { + wallet_id, + last_cleaned_time, + public_key, + }) + } +} + +impl TryFrom for Cell { + type Error = TonCellError; + + fn try_from(value: WalletDataHighloadV2R2) -> Result { + CellBuilder::new() + .store_i32(32, value.wallet_id)? + // TODO: not sure what goes into last_cleaned_time, so I set it to 0 + .store_u64(64, value.last_cleaned_time)? + .store_slice(&value.public_key)? + // empty plugin dict + .store_bit(false)? + .build() + } +} diff --git a/token-core/tcx-migration/src/migration.rs b/token-core/tcx-migration/src/migration.rs index 256ecf5b..3ab9be69 100644 --- a/token-core/tcx-migration/src/migration.rs +++ b/token-core/tcx-migration/src/migration.rs @@ -402,6 +402,7 @@ mod tests { curve: CurveType::SECP256k1, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; keystore.unlock(&key).unwrap(); @@ -460,6 +461,7 @@ mod tests { curve: CurveType::SECP256k1, network: "TESTNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; keystore.unlock_by_password("imtoken1").unwrap(); @@ -487,6 +489,7 @@ mod tests { curve: CurveType::SECP256k1, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; keystore.unlock(&key).unwrap(); @@ -513,6 +516,7 @@ mod tests { curve: CurveType::SECP256k1, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; keystore.unlock(&key).unwrap(); @@ -537,6 +541,7 @@ mod tests { curve: CurveType::SECP256k1, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; keystore.unlock(&key).unwrap(); @@ -561,6 +566,7 @@ mod tests { curve: CurveType::SECP256k1, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; keystore.unlock(&key).unwrap(); @@ -589,6 +595,7 @@ mod tests { curve: CurveType::SECP256k1, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; keystore.unlock(&key).unwrap(); diff --git a/token-core/tcx-proto/build.rs b/token-core/tcx-proto/build.rs index 334de988..7d672d57 100644 --- a/token-core/tcx-proto/build.rs +++ b/token-core/tcx-proto/build.rs @@ -53,4 +53,8 @@ fn main() { //tcx-eth env::set_var("OUT_DIR", "../tcx-eth/src"); prost_build::compile_protos(&["src/eth.proto"], &["src/"]).unwrap(); + + //tcx-ton + env::set_var("OUT_DIR", "../tcx-ton/src"); + prost_build::compile_protos(&["src/ton.proto"], &["src/"]).unwrap(); } diff --git a/token-core/tcx-proto/src/params.proto b/token-core/tcx-proto/src/params.proto index ea672cf1..414afa73 100644 --- a/token-core/tcx-proto/src/params.proto +++ b/token-core/tcx-proto/src/params.proto @@ -75,6 +75,7 @@ message DeriveAccountsParam { string segWit = 4; string chainId = 5; string curve = 6; + string contractCode = 7; } repeated Derivation derivations = 4; } diff --git a/token-core/tcx-proto/src/ton.proto b/token-core/tcx-proto/src/ton.proto new file mode 100644 index 00000000..9c77ee55 --- /dev/null +++ b/token-core/tcx-proto/src/ton.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; +package transaction; + +message TonRawTxIn { + string hash = 1; +} + +message TonTxOut { + string signature = 1; +} \ No newline at end of file diff --git a/token-core/tcx-substrate/src/address.rs b/token-core/tcx-substrate/src/address.rs index 1decc03c..747bda07 100644 --- a/token-core/tcx-substrate/src/address.rs +++ b/token-core/tcx-substrate/src/address.rs @@ -77,6 +77,7 @@ mod test_super { curve: CurveType::SR25519, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, ), ( @@ -88,6 +89,7 @@ mod test_super { curve: CurveType::SR25519, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, ), ]; @@ -107,6 +109,7 @@ mod test_super { curve: CurveType::SR25519, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let addr = SubstrateAddress::from_public_key(&typed_key, &kusama_coin_info).unwrap(); assert_eq!( @@ -128,6 +131,7 @@ mod test_super { curve: CurveType::SR25519, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let addresses = vec![ "FwMF8FdFKxPtt9enzZ2Zf7dJCxiu4HqK6GhRAsKCvbNkSqx", @@ -156,6 +160,7 @@ mod test_super { curve: CurveType::SR25519, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let addresses = vec![ "3BMEXohjFLZJGBLkCbF9zreee1eJjoM3ZB", diff --git a/token-core/tcx-tezos/src/address.rs b/token-core/tcx-tezos/src/address.rs index 06afda5f..a46b797e 100644 --- a/token-core/tcx-tezos/src/address.rs +++ b/token-core/tcx-tezos/src/address.rs @@ -96,6 +96,7 @@ mod test { curve: CurveType::ED25519, network: "MAINNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let pub_key = TypedPublicKey::from_slice( @@ -135,6 +136,7 @@ mod test { curve: CurveType::ED25519, network: "MAINNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let address = "tz1dLEU3WfzCrDq2bvoEz4cfLP5wg4S7xNo9"; //valid address let valid_result = TezosAddress::is_valid(address, &coin_info); @@ -158,6 +160,7 @@ mod test { curve: CurveType::ED25519, network: "MAINNET".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; let pub_key = TypedPublicKey::from_slice( diff --git a/token-core/tcx-tezos/src/signer.rs b/token-core/tcx-tezos/src/signer.rs index aa438288..198e6c5a 100644 --- a/token-core/tcx-tezos/src/signer.rs +++ b/token-core/tcx-tezos/src/signer.rs @@ -1,4 +1,5 @@ use crate::transaction::{TezosRawTxIn, TezosTxOut}; +use anyhow::anyhow; use bitcoin::util::base58; use blake2b_simd::Params; use tcx_common::{FromHex, ToHex}; @@ -13,6 +14,11 @@ impl TraitTransactionSigner for Keystore { params: &SignatureParameters, tx: &TezosRawTxIn, ) -> Result { + let path_parts = params.derivation_path.split('/').collect::>(); + if path_parts.len() < 4 || path_parts[2] != "1729'" { + return Err(anyhow!("invalid_sign_path")); + } + let raw_data_bytes = if tx.raw_data.starts_with("0x") { tx.raw_data[2..].to_string() } else { diff --git a/token-core/tcx-ton/Cargo.toml b/token-core/tcx-ton/Cargo.toml new file mode 100644 index 00000000..c4ac6123 --- /dev/null +++ b/token-core/tcx-ton/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "tcx-ton" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tcx-keystore = { path = "../tcx-keystore" } +tcx-constants = { path = "../tcx-constants" } +tcx-primitive = { path = "../tcx-primitive" } +tcx-crypto = { path = "../tcx-crypto" } +tcx-common = { path = "../tcx-common" } +tonlib-core = { path = "../tcx-libs/tonlib-core" } + +anyhow = { version = "=1.0.79", features = [] } +prost = "=0.11.2" +prost-types = "=0.11.2" \ No newline at end of file diff --git a/token-core/tcx-ton/src/address.rs b/token-core/tcx-ton/src/address.rs new file mode 100644 index 00000000..e15c6cc5 --- /dev/null +++ b/token-core/tcx-ton/src/address.rs @@ -0,0 +1,200 @@ +use core::str::FromStr; +use tcx_constants::CoinInfo; +use tcx_keystore::{Address, Result}; +use tcx_primitive::TypedPublicKey; +use tonlib_core::{ + cell::StateInit, + types::TonAddress as TonAddressLib, + wallet::{WalletVersion, DEFAULT_WALLET_ID, DEFAULT_WALLET_ID_V5R1}, +}; + +#[derive(PartialEq, Eq, Clone)] +pub struct TonAddress(String); + +impl Address for TonAddress { + fn from_public_key(public_key: &TypedPublicKey, coin: &CoinInfo) -> Result { + let wallet_version = WalletVersion::from_code(&coin.contract_code)?; + + let wallet_id = match wallet_version { + WalletVersion::V5R1 => DEFAULT_WALLET_ID_V5R1, + _ => DEFAULT_WALLET_ID, + }; + + let is_testnet = match coin.network.as_str() { + "TESTNET" => true, + _ => false, + }; + + let data = wallet_version.initial_data(&public_key.to_bytes(), wallet_id)?; + let code = wallet_version.code()?; + let state_init_hash = StateInit::create_account_id(&code, &data)?; + + let addr = TonAddressLib::new(0, &state_init_hash); + //true:Non-bounceable false:Bounceable + let address = addr.to_base64_url_flags(true, is_testnet); + + Ok(TonAddress(address)) + } + + fn is_valid(address: &str, _coin: &CoinInfo) -> bool { + let result = TonAddressLib::from_base64_url_flags(address); + if result.is_ok() { + true + } else { + false + } + } +} + +impl FromStr for TonAddress { + type Err = anyhow::Error; + + fn from_str(s: &str) -> std::result::Result { + Ok(TonAddress(s.to_string())) + } +} + +impl ToString for TonAddress { + fn to_string(&self) -> String { + self.0.clone() + } +} + +#[cfg(test)] +mod test { + use tcx_constants::{CoinInfo, CurveType, TEST_MNEMONIC, TEST_PASSWORD}; + use tcx_keystore::{Address, HdKeystore, Keystore, Metadata}; + + use super::TonAddress; + + #[test] + fn test_ton_address() { + let hd = + HdKeystore::from_mnemonic(TEST_MNEMONIC, TEST_PASSWORD, Metadata::default()).unwrap(); + let mut keystore = Keystore::Hd(hd); + keystore.unlock_by_password(TEST_PASSWORD).unwrap(); + + let mut coin = CoinInfo { + coin: "TON".to_string(), + derivation_path: "m/44'/607'/0'".to_string(), + curve: CurveType::ED25519, + network: "TESTNET".to_string(), + seg_wit: "".to_string(), + chain_id: "".to_string(), + //V5R1 code + contract_code: "te6ccgECFAEAAoEAART/APSkE/S88sgLAQIBIAIDAgFIBAUBAvIOAtzQINdJwSCRW49jINcLHyCCEGV4dG69IYIQc2ludL2wkl8D4IIQZXh0brqOtIAg1yEB0HTXIfpAMPpE+Cj6RDBYvZFb4O1E0IEBQdch9AWDB/QOb6ExkTDhgEDXIXB/2zzgMSDXSYECgLmRMOBw4hAPAgEgBgcCASAICQAZvl8PaiaECAoOuQ+gLAIBbgoLAgFIDA0AGa3OdqJoQCDrkOuF/8AAGa8d9qJoQBDrkOuFj8AAF7Ml+1E0HHXIdcLH4AARsmL7UTQ1woAgAR4g1wsfghBzaWduuvLgin8PAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYEAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKERITAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNA=".to_string(), + }; + //V5R1-TESTNET + let acc = keystore + .derive_coin::(&coin) + .unwrap(); + assert_eq!( + "0QDBKGsYs49NgdqM4gMoiVMV9Re5hM-yy3nvR_4XB0ZbUHzx", + acc.address + ); + //V5R1-MAINNET + coin.network = "MAINNET".to_string(); + let acc = keystore + .derive_coin::(&coin) + .unwrap(); + assert_eq!( + "UQDBKGsYs49NgdqM4gMoiVMV9Re5hM-yy3nvR_4XB0ZbUMd7", + acc.address + ); + //V4R2-TESTNET + coin.contract_code = "te6cckECFAEAAtQAART/APSkE/S88sgLAQIBIAIDAgFIBAUE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8QERITAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNBgcCASAICQB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+gIZ9ADLaRfLH1Jgyz8gyYBA+wAGAIpQBIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UAXKwjiOCEGRzdHKDHrFwgBhQBcsFUAPPFiP6AhPLassfyz/JgED7AJJfA+ICASAKCwBZvSQrb2omhAgKBrkPoCGEcNQICEekk30pkQzmkD6f+YN4EoAbeBAUiYcVnzGEAgFYDA0AEbjJftRNDXCx+AA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIA4PABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AAG7SB/oA1NQi+QAFyMoHFcv/ydB3dIAYyMsFywIizxZQBfoCFMtrEszMyXP7AMhAFIEBCPRR8qcCAHCBAQjXGPoA0z/IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBsgQEI1xj6ANM/MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAAr0AMntVGliJeU=".to_string(); + coin.network = "TESTNET".to_string(); + let acc = keystore + .derive_coin::(&coin) + .unwrap(); + assert_eq!( + "0QAz8TQ4n_ktc--IpefK-d5ABVpTR-FciJgLZr9vUpEgaGxk", + acc.address + ); + //V4R2-MAINNET + coin.network = "MAINNET".to_string(); + let acc = keystore + .derive_coin::(&coin) + .unwrap(); + assert_eq!( + "UQAz8TQ4n_ktc--IpefK-d5ABVpTR-FciJgLZr9vUpEgaNfu", + acc.address + ); + //V4R1-TESTNET + coin.contract_code = "te6cckECFQEAAvUAART/APSkE/S88sgLAQIBIAIDAgFIBAUE+PKDCNcYINMf0x/THwL4I7vyY+1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8REhMUA+7QAdDTAwFxsJFb4CHXScEgkVvgAdMfIYIQcGx1Z70ighBibG5jvbAighBkc3RyvbCSXwPgAvpAMCD6RAHIygfL/8nQ7UTQgQFA1yH0BDBcgQEI9ApvoTGzkl8F4ATTP8glghBwbHVnupEx4w0kghBibG5juuMABAYHCAIBIAkKAFAB+gD0BDCCEHBsdWeDHrFwgBhQBcsFJ88WUAP6AvQAEstpyx9SEMs/AFL4J28ighBibG5jgx6xcIAYUAXLBSfPFiT6AhTLahPLH1Iwyz8B+gL0AACSghBkc3Ryuo41BIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UghBkc3Rygx6xcIAYUATLBVjPFiL6AhLLassfyz+UEDRfBOLJgED7AAIBIAsMAFm9JCtvaiaECAoGuQ+gIYRw1AgIR6STfSmRDOaQPp/5g3gSgBt4EBSJhxWfMYQCAVgNDgARuMl+1E0NcLH4AD2ynftRNCBAUDXIfQEMALIygfL/8nQAYEBCPQKb6ExgAgEgDxAAGa3OdqJoQCBrkOuF/8AAGa8d9qJoQBBrkOuFj8AAbtIH+gDU1CL5AAXIygcVy//J0Hd0gBjIywXLAiLPFlAF+gIUy2sSzMzJcfsAyEAUgQEI9FHypwIAbIEBCNcYyFQgJYEBCPRR8qeCEG5vdGVwdIAYyMsFywJQBM8WghAF9eEA+gITy2oSyx/JcfsAAgBygQEI1xgwUgKBAQj0WfKn+CWCEGRzdHJwdIAYyMsFywJQBc8WghAF9eEA+gIUy2oTyx8Syz/Jc/sAAAr0AMntVEap808=".to_string(); + coin.network = "TESTNET".to_string(); + let acc = keystore + .derive_coin::(&coin) + .unwrap(); + assert_eq!( + "0QC1hu4IopANSj01rqYO2Omy8uAKz_QDCkxzs2AwXdl2Pyw8", + acc.address + ); + //V4R1-MAINNET + coin.network = "MAINNET".to_string(); + let acc = keystore + .derive_coin::(&coin) + .unwrap(); + assert_eq!( + "UQC1hu4IopANSj01rqYO2Omy8uAKz_QDCkxzs2AwXdl2P5e2", + acc.address + ); + //V3R1-TESTNET + coin.contract_code = "te6cckEBAQEAYgAAwP8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVD++buA=".to_string(); + coin.network = "TESTNET".to_string(); + let acc = keystore + .derive_coin::(&coin) + .unwrap(); + assert_eq!( + "0QCXvH7F6nL71NHulDevmD8jxta-YlED42hHy_rr9KI1Bjbu", + acc.address + ); + //V3R1-MAINNET + coin.network = "MAINNET".to_string(); + let acc = keystore + .derive_coin::(&coin) + .unwrap(); + assert_eq!( + "UQCXvH7F6nL71NHulDevmD8jxta-YlED42hHy_rr9KI1Bo1k", + acc.address + ); + //V3R2-TESTNET + coin.contract_code = "te6cckEBAQEAcQAA3v8AIN0gggFMl7ohggEznLqxn3Gw7UTQ0x/THzHXC//jBOCk8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVBC9ba0=".to_string(); + coin.network = "TESTNET".to_string(); + let acc = keystore + .derive_coin::(&coin) + .unwrap(); + assert_eq!( + "0QCg7HwngbcHdJN0QQ4u9KiIXba2FS7A7sbME4zGHXm-78Tv", + acc.address + ); + //V3R2-MAINNET + coin.network = "MAINNET".to_string(); + let acc = keystore + .derive_coin::(&coin) + .unwrap(); + assert_eq!( + "UQCg7HwngbcHdJN0QQ4u9KiIXba2FS7A7sbME4zGHXm-739l", + acc.address + ); + } + + #[test] + fn test_is_valid() { + let coin = CoinInfo { + coin: "TON".to_string(), + derivation_path: "m/44'/607'/0'".to_string(), + curve: CurveType::ED25519, + network: "TESTNET".to_string(), + seg_wit: "".to_string(), + chain_id: "".to_string(), + contract_code: "".to_string(), + }; + let result = + TonAddress::is_valid("UQDBKGsYs49NgdqM4gMoiVMV9Re5hM-yy3nvR_4XB0ZbUMd7", &coin); + assert!(result); + let result = + TonAddress::is_valid("UQDBKGsYs49NgdqM4gMoiVMV9Re5hM-yy3nvR_4XB0ZbUMd8", &coin); + assert!(!result) + } +} diff --git a/token-core/tcx-ton/src/lib.rs b/token-core/tcx-ton/src/lib.rs new file mode 100644 index 00000000..fb4c6cab --- /dev/null +++ b/token-core/tcx-ton/src/lib.rs @@ -0,0 +1,11 @@ +pub mod address; +pub mod signer; +pub mod transaction; + +pub mod ton { + pub const CHAINS: [&str; 1] = ["TON"]; + + pub type Address = crate::address::TonAddress; + pub type TransactionInput = crate::transaction::TonRawTxIn; + pub type TransactionOutput = crate::transaction::TonTxOut; +} diff --git a/token-core/tcx-ton/src/signer.rs b/token-core/tcx-ton/src/signer.rs new file mode 100644 index 00000000..8bbb79cc --- /dev/null +++ b/token-core/tcx-ton/src/signer.rs @@ -0,0 +1,115 @@ +use crate::transaction::{TonRawTxIn, TonTxOut}; +use anyhow::anyhow; +use tcx_common::{FromHex, ToHex as OtherToHex}; +use tcx_constants::Result; +use tcx_keystore::{ + Keystore, SignatureParameters, Signer, TransactionSigner as TraitTransactionSigner, +}; + +impl TraitTransactionSigner for Keystore { + fn sign_transaction( + &mut self, + params: &SignatureParameters, + tx: &TonRawTxIn, + ) -> Result { + if tx.hash.is_empty() { + return Err(anyhow!("invalid_sign_hash")); + } + + let path_parts = params.derivation_path.split('/').collect::>(); + if path_parts.len() < 4 || path_parts[2] != "607'" { + return Err(anyhow!("invalid_sign_path")); + } + + let hash = Vec::from_hex_auto(&tx.hash)?; + let sig = self.ed25519_sign(&hash.to_vec(), ¶ms.derivation_path)?; + + Ok(TonTxOut { + signature: sig.to_0x_hex(), + }) + } +} + +#[cfg(test)] +mod test_super { + use crate::transaction::TonRawTxIn; + use tcx_constants::{CoinInfo, CurveType, TEST_MNEMONIC, TEST_PASSWORD}; + use tcx_keystore::{HdKeystore, Keystore, Metadata, SignatureParameters, TransactionSigner}; + + #[test] + fn test_ton_sign() { + let hd = + HdKeystore::from_mnemonic(TEST_MNEMONIC, TEST_PASSWORD, Metadata::default()).unwrap(); + let path = "m/44'/607'/0'".to_string(); + let coin = CoinInfo { + coin: "TON".to_string(), + derivation_path: path.clone(), + curve: CurveType::ED25519, + network: "MAINNET".to_string(), + seg_wit: "".to_string(), + chain_id: "".to_string(), + contract_code: "te6ccgECFAEAAoEAART/APSkE/S88sgLAQIBIAIDAgFIBAUBAvIOAtzQINdJwSCRW49jINcLHyCCEGV4dG69IYIQc2ludL2wkl8D4IIQZXh0brqOtIAg1yEB0HTXIfpAMPpE+Cj6RDBYvZFb4O1E0IEBQdch9AWDB/QOb6ExkTDhgEDXIXB/2zzgMSDXSYECgLmRMOBw4hAPAgEgBgcCASAICQAZvl8PaiaECAoOuQ+gLAIBbgoLAgFIDA0AGa3OdqJoQCDrkOuF/8AAGa8d9qJoQBDrkOuFj8AAF7Ml+1E0HHXIdcLH4AARsmL7UTQ1woAgAR4g1wsfghBzaWduuvLgin8PAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYEAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKERITAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNA=".to_string(), + }; + + let mut keystore = Keystore::Hd(hd); + keystore.unlock_by_password(TEST_PASSWORD).unwrap(); + let acc = keystore + .derive_coin::(&coin) + .unwrap(); + assert_eq!( + "UQDBKGsYs49NgdqM4gMoiVMV9Re5hM-yy3nvR_4XB0ZbUMd7", + acc.address + ); + + let tx_in = TonRawTxIn { + hash: "0xd356774c21d6a6e2c651a5255f3f876fa973f1cfb7dce941c14ecabc2b1511d0".to_string(), + }; + + let param = SignatureParameters { + curve: CurveType::ED25519, + derivation_path: path.clone(), + chain_type: "TON".to_string(), + network: "MAINNET".to_string(), + seg_wit: "".to_string(), + }; + + let sign_ret = keystore.sign_transaction(¶m, &tx_in).unwrap(); + assert_eq!(sign_ret.signature, "0x9771c1bf4c69630b69cc0f0ae38db635f4ff1d161badc0f70b257b5a8f6a387cd75b72361ebf67fc5803feccdbb22ade85d053d766ed3b7c7029509363990c02"); + } + + #[test] + fn test_exception_case() { + let hd = + HdKeystore::from_mnemonic(TEST_MNEMONIC, TEST_PASSWORD, Metadata::default()).unwrap(); + let mut keystore = Keystore::Hd(hd); + keystore.unlock_by_password(TEST_PASSWORD).unwrap(); + + let tx_in = TonRawTxIn { + hash: "".to_string(), + }; + + let param = SignatureParameters { + curve: CurveType::ED25519, + derivation_path: "m/44'/607'/0'".to_string(), + chain_type: "TON".to_string(), + network: "MAINNET".to_string(), + seg_wit: "".to_string(), + }; + + let sign_ret = keystore.sign_transaction(¶m, &tx_in); + assert!(sign_ret.is_err()); + + let param = SignatureParameters { + curve: CurveType::ED25519, + derivation_path: "m/44'/607/0'".to_string(), + chain_type: "TON".to_string(), + network: "MAINNET".to_string(), + seg_wit: "".to_string(), + }; + let tx_in = TonRawTxIn { + hash: "0xd356774c21d6a6e2c651a5255f3f876fa973f1cfb7dce941c14ecabc2b1511d0".to_string(), + }; + let sign_ret = keystore.sign_transaction(¶m, &tx_in); + assert!(sign_ret.is_err()); + } +} diff --git a/token-core/tcx-ton/src/transaction.rs b/token-core/tcx-ton/src/transaction.rs new file mode 100644 index 00000000..1eb0766f --- /dev/null +++ b/token-core/tcx-ton/src/transaction.rs @@ -0,0 +1,12 @@ +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TonRawTxIn { + #[prost(string, tag = "1")] + pub hash: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TonTxOut { + #[prost(string, tag = "1")] + pub signature: ::prost::alloc::string::String, +} diff --git a/token-core/tcx-tron/src/address.rs b/token-core/tcx-tron/src/address.rs index 2158a70d..f775945d 100644 --- a/token-core/tcx-tron/src/address.rs +++ b/token-core/tcx-tron/src/address.rs @@ -67,6 +67,7 @@ mod tests { curve: CurveType::SECP256k1, network: "".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }; assert_eq!( @@ -135,6 +136,7 @@ mod tests { curve: CurveType::SECP256k1, network: "MAINNET".to_string(), seg_wit: "NONE".to_string(), + contract_code: "".to_string(), }; let address = TronAddress::from_public_key(&pub_key, &coin_info) .unwrap() diff --git a/token-core/tcx-tron/src/signer.rs b/token-core/tcx-tron/src/signer.rs index e6af9244..b8d1fc15 100644 --- a/token-core/tcx-tron/src/signer.rs +++ b/token-core/tcx-tron/src/signer.rs @@ -46,6 +46,11 @@ impl TraitTransactionSigner for Keystore { sign_context: &SignatureParameters, tx: &TronTxInput, ) -> Result { + let path_parts = sign_context.derivation_path.split('/').collect::>(); + if path_parts.len() < 4 || path_parts[2] != "195'" { + return Err(anyhow!("invalid_sign_path")); + } + let data = Vec::from_hex(&tx.raw_data)?; let hash = Hash::hash(&data); @@ -113,13 +118,13 @@ mod tests { let sign_context = SignatureParameters { curve: CurveType::SECP256k1, - derivation_path: "m/44'/145'/0'/0/0".to_string(), + derivation_path: "m/44'/195'/0'/0/0".to_string(), chain_type: "TRON".to_string(), ..Default::default() }; let signed_tx: TronTxOutput = ks.sign_transaction(&sign_context, &tx)?; - assert_eq!(signed_tx.signatures[0], "beac4045c3ea5136b541a3d5ec2a3e5836d94f28a1371440a01258808612bc161b5417e6f5a342451303cda840f7e21bfaba1011fad5f63538cb8cc132a9768800"); + assert_eq!(signed_tx.signatures[0], "c65b4bde808f7fcfab7b0ef9c1e3946c83311f8ac0a5e95be2d8b6d2400cfe8b5e24dc8f0883132513e422f2aaad8a4ecc14438eae84b2683eefa626e3adffc601"); Ok(()) } diff --git a/token-core/tcx/Cargo.toml b/token-core/tcx/Cargo.toml index d895c960..fa996019 100644 --- a/token-core/tcx/Cargo.toml +++ b/token-core/tcx/Cargo.toml @@ -22,6 +22,7 @@ tcx-eth2 = { path = "../tcx-eth2" } tcx-eth = { path = "../tcx-eth" } tcx-common = { path = "../tcx-common" } tcx-migration = { path = "../tcx-migration" } +tcx-ton = { path = "../tcx-ton" } #zksync-crypto = { git="https://github.com/consenlabs/zksync", branch="hotfix/compatible_with_tcx" } prost = "=0.11.2" diff --git a/token-core/tcx/src/api.rs b/token-core/tcx/src/api.rs index f7564f28..fb46a625 100644 --- a/token-core/tcx/src/api.rs +++ b/token-core/tcx/src/api.rs @@ -380,6 +380,8 @@ pub mod derive_accounts_param { pub chain_id: ::prost::alloc::string::String, #[prost(string, tag = "6")] pub curve: ::prost::alloc::string::String, + #[prost(string, tag = "7")] + pub contract_code: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Oneof)] diff --git a/token-core/tcx/src/handler.rs b/token-core/tcx/src/handler.rs index 33c97af7..5d90c7d9 100644 --- a/token-core/tcx/src/handler.rs +++ b/token-core/tcx/src/handler.rs @@ -88,6 +88,7 @@ use_chains!( tcx_substrate::polkadot, tcx_tezos::tezos, tcx_tron::tron, + tcx_ton::ton, ); pub fn encode_message(msg: impl Message) -> Result> { @@ -105,6 +106,7 @@ fn derive_account(keystore: &mut Keystore, derivation: &Derivation) -> Result Result<(CoinInfo, String)> { curve: CurveType::SECP256k1, network, seg_wit, + contract_code: "".to_string(), }; return Ok((coin_info, address)); } else { @@ -148,6 +149,7 @@ fn parse_coin_info_from_legacy_tcx_ks(legacy_tcx_ks: Value) -> Result<(CoinInfo, curve, network, seg_wit, + contract_code: "".to_string(), }; return Ok((coin_info, address)); } else { diff --git a/token-core/tcx/tests/derive_test.rs b/token-core/tcx/tests/derive_test.rs index 80beab6c..3d2c25cd 100644 --- a/token-core/tcx/tests/derive_test.rs +++ b/token-core/tcx/tests/derive_test.rs @@ -49,6 +49,7 @@ pub fn test_derive_accounts() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "LITECOIN".to_string(), @@ -57,6 +58,7 @@ pub fn test_derive_accounts() { seg_wit: "P2WPKH".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "LITECOIN".to_string(), @@ -65,6 +67,7 @@ pub fn test_derive_accounts() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "TRON".to_string(), @@ -73,6 +76,7 @@ pub fn test_derive_accounts() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "NERVOS".to_string(), @@ -81,6 +85,7 @@ pub fn test_derive_accounts() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "KUSAMA".to_string(), @@ -89,6 +94,7 @@ pub fn test_derive_accounts() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "sr25519".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "POLKADOT".to_string(), @@ -97,6 +103,7 @@ pub fn test_derive_accounts() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "sr25519".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "FILECOIN".to_string(), @@ -105,6 +112,7 @@ pub fn test_derive_accounts() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "FILECOIN".to_string(), @@ -113,6 +121,7 @@ pub fn test_derive_accounts() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "bls12-381".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "COSMOS".to_string(), @@ -121,6 +130,7 @@ pub fn test_derive_accounts() { seg_wit: "".to_string(), chain_id: "cosmoshub-4".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "EOS".to_string(), @@ -129,6 +139,7 @@ pub fn test_derive_accounts() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "ETHEREUM".to_string(), @@ -137,6 +148,7 @@ pub fn test_derive_accounts() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "BITCOIN".to_string(), @@ -145,6 +157,7 @@ pub fn test_derive_accounts() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "BITCOIN".to_string(), @@ -153,6 +166,7 @@ pub fn test_derive_accounts() { seg_wit: "P2WPKH".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "BITCOIN".to_string(), @@ -161,6 +175,7 @@ pub fn test_derive_accounts() { seg_wit: "VERSION_0".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "BITCOIN".to_string(), @@ -169,6 +184,7 @@ pub fn test_derive_accounts() { seg_wit: "VERSION_1".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "TEZOS".to_string(), @@ -177,6 +193,7 @@ pub fn test_derive_accounts() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "ed25519".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "DOGECOIN".to_string(), @@ -185,6 +202,7 @@ pub fn test_derive_accounts() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "DOGECOIN".to_string(), @@ -193,6 +211,7 @@ pub fn test_derive_accounts() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "DOGECOIN".to_string(), @@ -201,6 +220,16 @@ pub fn test_derive_accounts() { seg_wit: "VERSION_1".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), + }, + Derivation { + chain_type: "TON".to_string(), + path: "m/44'/607'/0'".to_string(), + network: "MAINNET".to_string(), + seg_wit: "".to_string(), + chain_id: "".to_string(), + curve: "ed25519".to_string(), + contract_code: "te6ccgECFAEAAoEAART/APSkE/S88sgLAQIBIAIDAgFIBAUBAvIOAtzQINdJwSCRW49jINcLHyCCEGV4dG69IYIQc2ludL2wkl8D4IIQZXh0brqOtIAg1yEB0HTXIfpAMPpE+Cj6RDBYvZFb4O1E0IEBQdch9AWDB/QOb6ExkTDhgEDXIXB/2zzgMSDXSYECgLmRMOBw4hAPAgEgBgcCASAICQAZvl8PaiaECAoOuQ+gLAIBbgoLAgFIDA0AGa3OdqJoQCDrkOuF/8AAGa8d9qJoQBDrkOuFj8AAF7Ml+1E0HHXIdcLH4AARsmL7UTQ1woAgAR4g1wsfghBzaWduuvLgin8PAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYEAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKERITAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNA=".to_string(), }, ]; @@ -214,7 +243,7 @@ pub fn test_derive_accounts() { let derived_accounts_bytes = call_api("derive_accounts", param).unwrap(); let derived_accounts: DeriveAccountsResult = DeriveAccountsResult::decode(derived_accounts_bytes.as_slice()).unwrap(); - assert_eq!(20, derived_accounts.accounts.len()); + assert_eq!(21, derived_accounts.accounts.len()); assert_eq!( "LQ3JqCohgLQ3x1CJXYERnJTy1ySaqr1E32", derived_accounts.accounts[0].address @@ -310,6 +339,10 @@ pub fn test_derive_accounts() { "1p37xv5xzd92c4wh8zt96f77a8jlf3n2qh4cps03xgurmpnllxy5us2dwgfl", derived_accounts.accounts[19].address ); + assert_eq!( + "UQDt6ko7K8TqHga_KO5fsKr-vrIz5EyuhpX1NrERE4UOg4CG", + derived_accounts.accounts[20].address + ); remove_created_wallet(&import_result.id); }) @@ -329,6 +362,7 @@ pub fn test_hd_store_derive_invalid_param() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "LITECOIN".to_string(), @@ -337,6 +371,7 @@ pub fn test_hd_store_derive_invalid_param() { seg_wit: "P2WPKH".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "LITECOIN".to_string(), @@ -345,6 +380,7 @@ pub fn test_hd_store_derive_invalid_param() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }, ]; for derivation in invalid_derivations { @@ -462,6 +498,7 @@ pub fn test_derive_btc_legacy_sub_accounts() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }; let (_wallet, accounts) = import_and_derive(derivation); @@ -503,6 +540,7 @@ pub fn test_derive_btc_p2wpkh_sub_accounts() { seg_wit: "P2WPKH".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }; let (_wallet, accounts) = import_and_derive(derivation); @@ -544,6 +582,7 @@ pub fn test_derive_eth_sub_accounts() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }; let (_, accounts) = import_and_derive(derivation); @@ -581,6 +620,7 @@ pub fn test_derive_cosmos_sub_accounts() { seg_wit: "".to_string(), chain_id: "cosmoshub-4".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }; let (_, accounts) = import_and_derive(derivation); @@ -639,6 +679,7 @@ pub fn test_derive_verify_hrp() { seg_wit: "".to_string(), chain_id: "osmosis-2".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }; let wallet = import_default_wallet(); @@ -659,6 +700,7 @@ pub fn test_derive_verify_hrp() { seg_wit: "".to_string(), chain_id: "cosmoshub-4".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }; let derivation_param = DeriveAccountsParam { @@ -775,6 +817,7 @@ fn polkadotjs_cross_test() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "sr25519".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "POLKADOT".to_string(), @@ -783,6 +826,7 @@ fn polkadotjs_cross_test() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "sr25519".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "POLKADOT".to_string(), @@ -791,6 +835,7 @@ fn polkadotjs_cross_test() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "sr25519".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "KUSAMA".to_string(), @@ -799,6 +844,7 @@ fn polkadotjs_cross_test() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "sr25519".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "KUSAMA".to_string(), @@ -807,6 +853,7 @@ fn polkadotjs_cross_test() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "sr25519".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "KUSAMA".to_string(), @@ -815,6 +862,7 @@ fn polkadotjs_cross_test() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "sr25519".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "KUSAMA".to_string(), @@ -823,6 +871,7 @@ fn polkadotjs_cross_test() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "sr25519".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "POLKADOT".to_string(), @@ -831,6 +880,7 @@ fn polkadotjs_cross_test() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "sr25519".to_string(), + contract_code: "".to_string(), }, ]; let param = DeriveAccountsParam { @@ -885,6 +935,7 @@ fn test_derive_other_curve_on_pk_keystore() { network: "".to_string(), curve: "secp256k1".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "FILECOIN".to_string(), @@ -893,6 +944,7 @@ fn test_derive_other_curve_on_pk_keystore() { network: "".to_string(), curve: "secp256k1".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }, ], key: Some(api::derive_accounts_param::Key::Password( @@ -919,6 +971,7 @@ fn test_derive_other_curve_on_pk_keystore() { network: "".to_string(), curve: "ed25519".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }], key: Some(api::derive_accounts_param::Key::Password( TEST_PASSWORD.to_string(), @@ -939,6 +992,7 @@ fn test_derive_other_curve_on_pk_keystore() { network: "".to_string(), curve: "sr25519".to_string(), seg_wit: "".to_string(), + contract_code: "".to_string(), }], key: Some(api::derive_accounts_param::Key::Password( TEST_PASSWORD.to_string(), @@ -976,6 +1030,7 @@ fn test_derive_mainnet_account_on_test_wif() { network: "TESTNET".to_string(), curve: "secp256k1".to_string(), seg_wit: "VERSION_1".to_string(), + contract_code: "".to_string(), }], key: Some(api::derive_accounts_param::Key::Password( TEST_PASSWORD.to_string(), diff --git a/token-core/tcx/tests/export_test.rs b/token-core/tcx/tests/export_test.rs index da1f67e9..d8b280e5 100644 --- a/token-core/tcx/tests/export_test.rs +++ b/token-core/tcx/tests/export_test.rs @@ -85,6 +85,7 @@ pub fn test_tezos_import_private_key_export() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }]; let param = DeriveAccountsParam { id: import_result.id.to_string(), @@ -165,6 +166,7 @@ pub fn test_tezos_hd_private_key_export() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "ed25519".to_string(), + contract_code: "".to_string(), }]; let param = DeriveAccountsParam { id: import_result.id.to_string(), @@ -276,6 +278,7 @@ pub fn test_export_private_key() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "BITCOINCASH".to_string(), @@ -284,6 +287,7 @@ pub fn test_export_private_key() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "TRON".to_string(), @@ -292,6 +296,7 @@ pub fn test_export_private_key() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "ETHEREUM".to_string(), @@ -300,6 +305,7 @@ pub fn test_export_private_key() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "FILECOIN".to_string(), @@ -308,6 +314,7 @@ pub fn test_export_private_key() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "BITCOIN".to_string(), @@ -316,6 +323,7 @@ pub fn test_export_private_key() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "BITCOIN".to_string(), @@ -324,6 +332,7 @@ pub fn test_export_private_key() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "BITCOIN".to_string(), @@ -332,6 +341,7 @@ pub fn test_export_private_key() { seg_wit: "P2WPKH".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "BITCOIN".to_string(), @@ -340,6 +350,7 @@ pub fn test_export_private_key() { seg_wit: "VERSION_0".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "BITCOIN".to_string(), @@ -348,6 +359,7 @@ pub fn test_export_private_key() { seg_wit: "VERSION_1".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "EOS".to_string(), @@ -356,6 +368,7 @@ pub fn test_export_private_key() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, ]; let pks = vec![ @@ -476,6 +489,7 @@ pub fn test_chain_cannot_export_private_key() { seg_wit: "".to_string(), chain_id: "cosmoshub-4".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }]; let export_info = vec![ diff --git a/token-core/tcx/tests/import_test.rs b/token-core/tcx/tests/import_test.rs index 6a73c837..0c03212f 100644 --- a/token-core/tcx/tests/import_test.rs +++ b/token-core/tcx/tests/import_test.rs @@ -83,6 +83,7 @@ pub fn test_import_mnemonic() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }; let param = DeriveAccountsParam { id: import_result.id.to_string(), @@ -151,6 +152,7 @@ pub fn test_import_mnemonic_ltc() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }; let param = DeriveAccountsParam { id: import_result.id.to_string(), @@ -199,6 +201,7 @@ pub fn test_import_private_key() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "LITECOIN".to_string(), @@ -207,6 +210,7 @@ pub fn test_import_private_key() { seg_wit: "P2WPKH".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "LITECOIN".to_string(), @@ -215,6 +219,7 @@ pub fn test_import_private_key() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "TRON".to_string(), @@ -223,6 +228,7 @@ pub fn test_import_private_key() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "NERVOS".to_string(), @@ -231,6 +237,7 @@ pub fn test_import_private_key() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "ETHEREUM".to_string(), @@ -239,6 +246,7 @@ pub fn test_import_private_key() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "COSMOS".to_string(), @@ -247,6 +255,7 @@ pub fn test_import_private_key() { seg_wit: "".to_string(), chain_id: "cosmoshub-4".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "EOS".to_string(), @@ -255,6 +264,7 @@ pub fn test_import_private_key() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "BITCOIN".to_string(), @@ -263,6 +273,7 @@ pub fn test_import_private_key() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "BITCOIN".to_string(), @@ -271,6 +282,7 @@ pub fn test_import_private_key() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "BITCOIN".to_string(), @@ -279,6 +291,7 @@ pub fn test_import_private_key() { seg_wit: "P2WPKH".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "BITCOIN".to_string(), @@ -287,6 +300,7 @@ pub fn test_import_private_key() { seg_wit: "VERSION_0".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "BITCOIN".to_string(), @@ -295,6 +309,7 @@ pub fn test_import_private_key() { seg_wit: "VERSION_1".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, Derivation { chain_type: "DOGECOIN".to_string(), @@ -303,6 +318,7 @@ pub fn test_import_private_key() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }, ]; let param = DeriveAccountsParam { @@ -398,6 +414,7 @@ pub fn test_import_private_key() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }]; let param = DeriveAccountsParam { id: import_result.id.to_string(), @@ -443,6 +460,7 @@ pub fn test_filecoin_private_key_secp256k1_import() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }]; let param = DeriveAccountsParam { id: import_result.id.to_string(), @@ -509,6 +527,7 @@ pub fn test_filecoin_private_key_bls_import() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "bls12-381".to_string(), + contract_code: "".to_string(), }]; let param = DeriveAccountsParam { id: import_result.id.to_string(), @@ -576,6 +595,7 @@ pub fn test_fil_bls_tezos_reimport() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: case.2.to_string(), + contract_code: "".to_string(), }]; let param = DeriveAccountsParam { id: hd_import_result.id.to_string(), @@ -621,6 +641,7 @@ pub fn test_fil_bls_tezos_reimport() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: case.2.to_string(), + contract_code: "".to_string(), }]; let param = DeriveAccountsParam { id: pk_import_result.id.to_string(), @@ -669,6 +690,7 @@ pub fn test_import_sr25519_private_key() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "sr25519".to_string(), + contract_code: "".to_string(), }]; let param = DeriveAccountsParam { id: import_result.id.to_string(), @@ -747,6 +769,7 @@ pub fn test_import_to_pk_which_from_hd() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }; let derive_param = DeriveAccountsParam { @@ -846,6 +869,7 @@ pub fn test_import_substrate_keystore() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }; let param = DeriveAccountsParam { @@ -937,6 +961,7 @@ pub fn test_import_substrate_keystore_v3() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "sr25519".to_string(), + contract_code: "".to_string(), }; let param = DeriveAccountsParam { @@ -1020,6 +1045,7 @@ pub fn test_import_multi_curve() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }; let param = DeriveAccountsParam { @@ -1045,6 +1071,7 @@ pub fn test_import_multi_curve() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }; let param = DeriveAccountsParam { @@ -1110,6 +1137,7 @@ pub fn test_import_wif() { seg_wit: "NONE".to_string(), chain_id: "".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }; let param = DeriveAccountsParam { id: import_result.id.to_string(), diff --git a/token-core/tcx/tests/sign_test.rs b/token-core/tcx/tests/sign_test.rs index 987bf598..99a9ddcb 100644 --- a/token-core/tcx/tests/sign_test.rs +++ b/token-core/tcx/tests/sign_test.rs @@ -41,6 +41,7 @@ use tcx_eth::transaction::{ use tcx_filecoin::{SignedMessage, UnsignedMessage}; use tcx_substrate::{SubstrateRawTxIn, SubstrateTxOut}; use tcx_tezos::transaction::{TezosRawTxIn, TezosTxOut}; +use tcx_ton::transaction::{TonRawTxIn, TonTxOut}; use tcx_tron::transaction::{TronMessageInput, TronMessageOutput, TronTxInput, TronTxOutput}; #[test] @@ -321,6 +322,7 @@ pub fn test_sign_tron_tx_by_pk() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }; let param = DeriveAccountsParam { id: import_result.id.to_string(), @@ -743,6 +745,7 @@ pub fn test_lock_after_sign() { seg_wit: "".to_string(), chain_id: "".to_string(), curve: "".to_string(), + contract_code: "".to_string(), }; let (wallet, _acc_rsp) = import_and_derive(derivation); @@ -952,6 +955,7 @@ pub fn test_sign_ethereum_legacy_tx() { seg_wit: "".to_string(), chain_id: "1".to_string(), curve: "secp256k1".to_string(), + contract_code: "".to_string(), }; let (wallet, acc_rsp) = import_and_derive(derivation); @@ -1262,3 +1266,37 @@ Issued At: 2024-02-27T06:45:12.725Z".to_string(), assert_eq!(ret.signatures[0], "0xa30a252dd84370a22035b1bdd43e594bb54c3e874f3cee2b4c12e16392feb3c13d4589d3f2b006fcad20426a4cb2b16c3523cc82705d1e447a9926cb1e2398481c".to_string()); }); } + +#[test] +#[serial] +pub fn test_sign_ton_tx() { + run_test(|| { + let wallet = import_default_wallet(); + + let hash = "0xd356774c21d6a6e2c651a5255f3f876fa973f1cfb7dce941c14ecabc2b1511d0".to_string(); + let input = TonRawTxIn { hash }; + let input_value = encode_message(input).unwrap(); + + let tx = SignParam { + id: wallet.id.to_string(), + key: Some(Key::Password(TEST_PASSWORD.to_string())), + chain_type: "TON".to_string(), + path: "m/44'/607'/0'".to_string(), + curve: "ed25519".to_string(), + network: "MAINNET".to_string(), + seg_wit: "".to_string(), + input: Some(::prost_types::Any { + type_url: "imtoken".to_string(), + value: input_value, + }), + }; + + let ret = call_api("sign_tx", tx.clone()).unwrap(); + + let output: TonTxOut = TonTxOut::decode(ret.as_slice()).unwrap(); + let expected_sign = "0x9771c1bf4c69630b69cc0f0ae38db635f4ff1d161badc0f70b257b5a8f6a387cd75b72361ebf67fc5803feccdbb22ade85d053d766ed3b7c7029509363990c02"; + assert_eq!(expected_sign, output.signature.as_str()); + + remove_created_wallet(&wallet.id); + }) +}