diff --git a/Cargo.lock b/Cargo.lock index a27545e2..0972fb1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3173,9 +3173,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac 0.12.1", "password-hash 0.5.0", - "sha2 0.10.8", ] [[package]] @@ -5103,11 +5101,6 @@ name = "tcx-ton" version = "0.1.0" dependencies = [ "anyhow", - "bitcoin", - "blake2b_simd 1.0.1", - "bytes", - "hex", - "num-bigint 0.4.3", "prost", "prost-types", "tcx-common", @@ -5115,7 +5108,7 @@ dependencies = [ "tcx-crypto", "tcx-keystore", "tcx-primitive", - "tonlib-core 0.19.1", + "tonlib-core", ] [[package]] @@ -5395,28 +5388,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tonlib-core" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872a87c2489d8867aa2089b163f2f7ed34c8ef052dace0a82a1892447703ed9b" -dependencies = [ - "base64 0.22.1", - "bitstream-io", - "crc", - "hex", - "hmac 0.12.1", - "lazy_static", - "nacl", - "num-bigint 0.4.3", - "num-traits", - "pbkdf2 0.12.2", - "serde", - "serde_json", - "sha2 0.10.8", - "thiserror", -] - [[package]] name = "tonlib-core" version = "0.21.1" diff --git a/token-core/tcx-libs/tonlib-core/src/cell/dict.rs b/token-core/tcx-libs/tonlib-core/src/cell/dict.rs index c7d96def..8ac568ae 100644 --- a/token-core/tcx-libs/tonlib-core/src/cell/dict.rs +++ b/token-core/tcx-libs/tonlib-core/src/cell/dict.rs @@ -8,6 +8,3 @@ mod types; pub(crate) use builder::DictBuilder; pub(crate) use parser::DictParser; pub use types::{KeyReader, SnakeFormatDict, ValReader, ValWriter}; - -#[cfg(test)] -mod tests; diff --git a/token-core/tcx-libs/tonlib-core/src/cell/dict/tests.rs b/token-core/tcx-libs/tonlib-core/src/cell/dict/tests.rs deleted file mode 100644 index 7290bc40..00000000 --- a/token-core/tcx-libs/tonlib-core/src/cell/dict/tests.rs +++ /dev/null @@ -1,232 +0,0 @@ -// tests cover parser & builder together, so make sense to keep them in the same module -use std::collections::HashMap; -use std::ops::Deref; - -use num_bigint::BigUint; -use tokio_test::assert_ok; - -use crate::cell::dict::predefined_readers::{ - key_reader_256bit, key_reader_u16, key_reader_u32, key_reader_u64, key_reader_u8, - key_reader_uint, val_reader_ref_cell, val_reader_uint, -}; -use crate::cell::dict::predefined_writers::{val_writer_ref_cell, val_writer_unsigned_min_size}; -use crate::cell::{ArcCell, BagOfCells, Cell, CellBuilder}; - -#[test] -fn test_blockchain_data() -> anyhow::Result<()> { - let expected_data = HashMap::from([ - (0u8, BigUint::from(25965603044000000000u128)), - (1, BigUint::from(5173255344000000000u64)), - (2, BigUint::from(344883687000000000u64)), - ]); - let boc_b64 = "te6cckEBBgEAWgABGccNPKUADZm5MepOjMABAgHNAgMCASAEBQAnQAAAAAAAAAAAAAABMlF4tR2RgCAAJgAAAAAAAAAAAAABaFhaZZhr6AAAJgAAAAAAAAAAAAAAR8sYU4eC4AA1PIC5"; - let boc = BagOfCells::parse_base64(boc_b64)?; - let dict_cell = boc.single_root()?; - let mut parser = dict_cell.parser(); - let cell_data = parser.load_uint(96)?; - - let parsed_dict = assert_ok!(parser.load_dict(8, key_reader_u8, val_reader_uint)); - assert_eq!(expected_data, parsed_dict); - - let writer = |builder: &mut CellBuilder, val: BigUint| { - builder.store_uint(150, &val)?; // empirically found bit length - Ok(()) - }; - let mut builder = CellBuilder::new(); - builder.store_uint(96, &cell_data)?; - assert_ok!(builder.store_dict(8, writer, expected_data)); - let constructed_cell: Cell = builder.build()?; - assert_eq!(dict_cell.deref(), &constructed_cell); - Ok(()) -} - -#[test] -fn test_key_len_bigger_than_reader() -> anyhow::Result<()> { - let data = HashMap::from([ - (0u16, BigUint::from(4u32)), - (1, BigUint::from(5u32)), - (2, BigUint::from(6u32)), - (10u16, BigUint::from(7u32)), - (127, BigUint::from(8u32)), - ]); - - for key_len_bits in [8, 16, 32, 64, 111] { - let mut builder = CellBuilder::new(); - builder.store_dict(key_len_bits, val_writer_unsigned_min_size, data.clone())?; - let dict_cell = builder.build()?; - let parsed = dict_cell - .parser() - .load_dict(key_len_bits, key_reader_u16, val_reader_uint)?; - assert_eq!(data, parsed, "key_len_bits: {}", key_len_bits); - } - Ok(()) -} - -#[test] -fn test_reader_u8() -> anyhow::Result<()> { - let data = HashMap::from([ - (0u8, BigUint::from(4u32)), - (1, BigUint::from(5u32)), - (2, BigUint::from(6u32)), - (64, BigUint::from(7u32)), - ]); - let key_len_bits = 8; - let mut builder = CellBuilder::new(); - builder.store_dict(key_len_bits, val_writer_unsigned_min_size, data.clone())?; - let dict_cell = builder.build()?; - let parsed = dict_cell - .parser() - .load_dict(key_len_bits, key_reader_u8, val_reader_uint)?; - assert_eq!(data, parsed); - Ok(()) -} - -#[test] -fn test_reader_u16() -> anyhow::Result<()> { - let data = HashMap::from([ - (0u16, BigUint::from(4u32)), - (1, BigUint::from(5u32)), - (2, BigUint::from(6u32)), - (64, BigUint::from(7u32)), - ]); - let key_len_bits = 8; - let mut builder = CellBuilder::new(); - builder.store_dict(key_len_bits, val_writer_unsigned_min_size, data.clone())?; - let dict_cell = builder.build()?; - let parsed = dict_cell - .parser() - .load_dict(key_len_bits, key_reader_u16, val_reader_uint)?; - assert_eq!(data, parsed); - Ok(()) -} - -#[test] -fn test_reader_u32() -> anyhow::Result<()> { - let data = HashMap::from([ - (0u32, BigUint::from(4u32)), - (1, BigUint::from(5u32)), - (2, BigUint::from(6u32)), - (64, BigUint::from(7u32)), - ]); - let key_len_bits = 8; - let mut builder = CellBuilder::new(); - builder.store_dict(key_len_bits, val_writer_unsigned_min_size, data.clone())?; - let dict_cell = builder.build()?; - let parsed = dict_cell - .parser() - .load_dict(key_len_bits, key_reader_u32, val_reader_uint)?; - assert_eq!(data, parsed); - Ok(()) -} - -#[test] -fn test_reader_u64() -> anyhow::Result<()> { - let data = HashMap::from([ - (0u64, BigUint::from(4u32)), - (1, BigUint::from(5u32)), - (2, BigUint::from(6u32)), - (64, BigUint::from(7u32)), - ]); - let key_len_bits = 8; - let mut builder = CellBuilder::new(); - builder.store_dict(key_len_bits, val_writer_unsigned_min_size, data.clone())?; - let dict_cell = builder.build()?; - let parsed = dict_cell - .parser() - .load_dict(key_len_bits, key_reader_u64, val_reader_uint)?; - assert_eq!(data, parsed); - Ok(()) -} - -#[test] -fn test_reader_256bit() -> anyhow::Result<()> { - let bytes1 = [ - 1u8, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, - 4, 4, - ]; - let bytes2 = [ - 2u8, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, - 5, 5, - ]; - let bytes3 = [ - 3u8, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, - 6, 6, - ]; - let bytes4 = [ - 4u8, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, - 7, 7, - ]; - - let data_src = HashMap::from([ - (bytes1, BigUint::from(1u32)), - (bytes2, BigUint::from(2u32)), - (bytes3, BigUint::from(3u32)), - (bytes4, BigUint::from(4u32)), - ]); - - let data_serial = data_src - .iter() - .map(|(k, v)| (BigUint::from_bytes_be(k), v.clone())) - .collect::>(); - - let key_len_bits = 256; - let mut builder = CellBuilder::new(); - builder.store_dict(key_len_bits, val_writer_unsigned_min_size, data_serial)?; - - let dict_cell = builder.build()?; - let parsed = dict_cell - .parser() - .load_dict(key_len_bits, key_reader_256bit, val_reader_uint)?; - - assert_eq!(data_src, parsed); - Ok(()) -} - -#[test] -fn test_reader_uint() -> anyhow::Result<()> { - let data = HashMap::from([ - (BigUint::from(0u32), BigUint::from(4u32)), - (BigUint::from(1u32), BigUint::from(5u32)), - (BigUint::from(2u32), BigUint::from(6u32)), - (BigUint::from(64u32), BigUint::from(7u32)), - ]); - let key_len_bits = 8; - let mut builder = CellBuilder::new(); - builder.store_dict(key_len_bits, val_writer_unsigned_min_size, data.clone())?; - let dict_cell = builder.build()?; - let parsed = dict_cell - .parser() - .load_dict(key_len_bits, key_reader_uint, val_reader_uint)?; - assert_eq!(data, parsed); - Ok(()) -} - -#[test] -fn test_reader_cell() -> anyhow::Result<()> { - let data = HashMap::from([ - ( - BigUint::from(0u32), - ArcCell::new(Cell::new(vec![0], 20, vec![], false)?), - ), - ( - BigUint::from(1u32), - ArcCell::new(Cell::new(vec![1], 20, vec![], false)?), - ), - ( - BigUint::from(2u32), - ArcCell::new(Cell::new(vec![2], 20, vec![], false)?), - ), - ( - BigUint::from(6u32), - ArcCell::new(Cell::new(vec![6], 20, vec![], false)?), - ), - ]); - let key_len_bits = 8; - let mut builder = CellBuilder::new(); - builder.store_dict(key_len_bits, val_writer_ref_cell, data.clone())?; - let dict_cell = builder.build()?; - let mut parser = dict_cell.parser(); - let parsed = parser.load_dict(key_len_bits, key_reader_uint, val_reader_ref_cell)?; - assert_eq!(data, parsed); - Ok(()) -} 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 index c2546b91..172764e2 100644 --- a/token-core/tcx-libs/tonlib-core/src/cell/state_init.rs +++ b/token-core/tcx-libs/tonlib-core/src/cell/state_init.rs @@ -66,64 +66,3 @@ impl StateInit { .cell_hash()) } } - -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use super::StateInitBuilder; - use crate::cell::{CellBuilder, TonCellError}; - - #[test] - fn test_state_init() -> Result<(), TonCellError> { - let code = Arc::new(CellBuilder::new().store_string("code")?.build()?); - let data = Arc::new(CellBuilder::new().store_string("data")?.build()?); - let state_init = StateInitBuilder::new(&code, &data) - .with_split_depth(true) - .with_tick_tock(true) - .with_library(true) - .build()?; - - assert_eq!(state_init.data[0], 0b11111000); - println!("{:08b}", state_init.data[0]); - - let code = Arc::new(CellBuilder::new().store_string("code")?.build()?); - let data = Arc::new(CellBuilder::new().store_string("data")?.build()?); - let state_init = StateInitBuilder::new(&code, &data) - .with_split_depth(false) - .with_tick_tock(false) - .with_library(false) - .build()?; - - assert_eq!(state_init.data[0], 0b00110000); - - let code = Arc::new(CellBuilder::new().store_string("code")?.build()?); - let data = Arc::new(CellBuilder::new().store_string("data")?.build()?); - let state_init = StateInitBuilder::new(&code, &data) - .with_split_depth(true) - .with_tick_tock(false) - .with_library(false) - .build()?; - - assert_eq!(state_init.data[0], 0b10110000); - - let code = Arc::new(CellBuilder::new().store_string("code")?.build()?); - let data = Arc::new(CellBuilder::new().store_string("data")?.build()?); - let state_init = StateInitBuilder::new(&code, &data) - .with_split_depth(false) - .with_tick_tock(true) - .with_library(false) - .build()?; - assert_eq!(state_init.data[0], 0b01110000); - - let code = Arc::new(CellBuilder::new().store_string("code")?.build()?); - let data = Arc::new(CellBuilder::new().store_string("data")?.build()?); - let state_init = StateInitBuilder::new(&code, &data) - .with_split_depth(false) - .with_tick_tock(false) - .with_library(true) - .build()?; - assert_eq!(state_init.data[0], 0b00111000); - Ok(()) - } -} diff --git a/token-core/tcx-libs/tonlib-core/src/lib.rs b/token-core/tcx-libs/tonlib-core/src/lib.rs index 4468d8e6..1211425d 100644 --- a/token-core/tcx-libs/tonlib-core/src/lib.rs +++ b/token-core/tcx-libs/tonlib-core/src/lib.rs @@ -3,8 +3,6 @@ pub mod cell; // pub mod message; // pub mod mnemonic; pub mod types; -// pub mod wallet; +pub mod wallet; -pub use crate::types::{ - TonAddress, TonAddressParseError, TonHash, -}; +pub use crate::types::{TonAddress, TonAddressParseError, TonHash}; 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..1b32d361 --- /dev/null +++ b/token-core/tcx-libs/tonlib-core/src/wallet.rs @@ -0,0 +1,173 @@ +mod types; + +use std::sync::Arc; + +use lazy_static::lazy_static; +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; + +lazy_static! { + pub static ref WALLET_V1R1_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/wallet_v1r1.code"); + BagOfCells::parse_base64(code).unwrap() + }; + pub static ref WALLET_V1R2_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/wallet_v1r2.code"); + BagOfCells::parse_base64(code).unwrap() + }; + pub static ref WALLET_V1R3_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/wallet_v1r3.code"); + BagOfCells::parse_base64(code).unwrap() + }; + pub static ref WALLET_V2R1_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/wallet_v2r1.code"); + BagOfCells::parse_base64(code).unwrap() + }; + pub static ref WALLET_V2R2_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/wallet_v2r2.code"); + BagOfCells::parse_base64(code).unwrap() + }; + pub static ref WALLET_V3R1_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/wallet_v3r1.code"); + BagOfCells::parse_base64(code).unwrap() + }; + pub static ref WALLET_V3R2_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/wallet_v3r2.code"); + BagOfCells::parse_base64(code).unwrap() + }; + pub static ref WALLET_V4R1_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/wallet_v4r1.code"); + BagOfCells::parse_base64(code).unwrap() + }; + pub static ref WALLET_V4R2_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/wallet_v4r2.code"); + BagOfCells::parse_base64(code).unwrap() + }; + pub static ref WALLET_V5R1_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/wallet_v5.code"); + BagOfCells::parse_base64(code).unwrap() + }; + pub static ref HIGHLOAD_V1R1_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/highload_v1r1.code"); + BagOfCells::parse_base64(code).unwrap() + }; + pub static ref HIGHLOAD_V1R2_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/highload_v1r2.code"); + BagOfCells::parse_base64(code).unwrap() + }; + pub static ref HIGHLOAD_V2_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/highload_v2.code"); + BagOfCells::parse_base64(code).unwrap() + }; + pub static ref HIGHLOAD_V2R1_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/highload_v2r1.code"); + BagOfCells::parse_base64(code).unwrap() + }; + pub static ref HIGHLOAD_V2R2_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/highload_v2r2.code"); + BagOfCells::parse_base64(code).unwrap() + }; +} + +#[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<&ArcCell, TonCellError> { + let code: &BagOfCells = 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, + }; + code.single_root() + } + + pub fn initial_data(&self, key: &[u8], wallet_id: i32) -> Result { + // let public_key: TonHash = key_pair + // .public_key + // .clone() + // .try_into() + // .map_err(|_| TonCellError::InternalError("Invalid public key size".to_string()))?; + + 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)) + } +} 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-proto/src/ton.proto b/token-core/tcx-proto/src/ton.proto index ee776641..9807b350 100644 --- a/token-core/tcx-proto/src/ton.proto +++ b/token-core/tcx-proto/src/ton.proto @@ -10,16 +10,4 @@ message TonRawTxIn { message TonTxOut { string signature = 1; -} - -message TonTxIn { - string from = 1; - string to = 2; - string amount = 3; - string memo = 4; - bool isJetton = 5; - string jettonAmount = 6; - uint64 queryId = 7; - int32 sequenceNo = 8; - string walletVersion = 9; } \ No newline at end of file diff --git a/token-core/tcx-ton/Cargo.toml b/token-core/tcx-ton/Cargo.toml index 8f1eee6e..c4ac6123 100644 --- a/token-core/tcx-ton/Cargo.toml +++ b/token-core/tcx-ton/Cargo.toml @@ -11,13 +11,8 @@ 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 = [] } -blake2b_simd = "=1.0.1" -hex = "=0.4.3" -bitcoin = "=0.29.2" prost = "=0.11.2" -bytes = "=1.4.0" -prost-types = "=0.11.2" -tonlib-core = "0.19.1" -num-bigint = "0.4.3" \ No newline at end of file +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 index 417a2467..29a58f4a 100644 --- a/token-core/tcx-ton/src/address.rs +++ b/token-core/tcx-ton/src/address.rs @@ -1,13 +1,9 @@ -use anyhow::anyhow; use core::str::FromStr; -use tonlib_core::mnemonic::KeyPair; -use tonlib_core::wallet::{TonWallet, WalletVersion}; - -// use bech32::{FromBase32, ToBase32, Variant}; -use tcx_common::{ripemd160, sha256}; +use tcx_common::ToHex; use tcx_constants::CoinInfo; use tcx_keystore::{Address, Result}; use tcx_primitive::TypedPublicKey; +use tonlib_core::{cell::StateInit, types::TonAddress as TonAddressLib, wallet::WalletVersion}; // size of address pub const LENGTH: usize = 20; @@ -18,19 +14,49 @@ pub struct TonAddress(String); impl Address for TonAddress { fn from_public_key(public_key: &TypedPublicKey, coin: &CoinInfo) -> Result { let pub_key_bytes = public_key.to_bytes(); - let key_pair = KeyPair { - public_key: pub_key_bytes.clone(), - secret_key: Vec::new(), + println!("{}", pub_key_bytes.clone().to_0x_hex()); + + let wallet_id: i32 = 0x29a9a317; + let wallet_version = WalletVersion::V4R2; + let workchain = 0; + + let non_production = match coin.network.as_str() { + "TESTNET" => true, + _ => false, }; - let wallet = TonWallet::derive_default(WalletVersion::V4R2, &key_pair)?; - // wallet.sign_external_body() + let data = wallet_version.initial_data(&pub_key_bytes, wallet_id)?; + let code = wallet_version.code()?; + let state_init_hash = StateInit::create_account_id(code, &data)?; - Ok(TonAddress(wallet.address.to_string())) + let addr = TonAddressLib::new(workchain, &state_init_hash); + let address = addr.to_base64_std_flags(false, non_production); + + let result = TonAddressLib::from_base64_std_flags(&address)?; + println!( + "workchain: {},hash_path: {}", + result.0.workchain, + result.0.hash_part.as_slice().to_0x_hex() + ); + println!("non_bounceable: {}", result.1); + println!("non_production: {}", result.2); + + Ok(TonAddress(address)) } fn is_valid(address: &str, coin: &CoinInfo) -> bool { - tonlib_core::TonAddress::from_str(address).is_ok() + let result = TonAddressLib::from_base64_std_flags(address); + if let Ok(ton_addr) = result { + if (ton_addr.2 && coin.network.eq_ignore_ascii_case("TESTNET")) + || (!ton_addr.2 && coin.network.eq_ignore_ascii_case("MAINNET")) + { + true + } else { + false + } + } else { + false + } } } @@ -47,3 +73,34 @@ impl ToString for TonAddress { self.0.clone() } } + +#[cfg(test)] +mod test { + use tcx_constants::{CoinInfo, CurveType, TEST_MNEMONIC, TEST_PASSWORD}; + use tcx_keystore::{HdKeystore, Keystore, Metadata}; + + #[test] + fn test_ton_address() { + let hd = + HdKeystore::from_mnemonic(TEST_MNEMONIC, TEST_PASSWORD, Metadata::default()).unwrap(); + let coin = CoinInfo { + coin: "TON".to_string(), + // todo: the ton path is not official + derivation_path: "m/44'/607'/0'".to_string(), + curve: CurveType::ED25519, + network: "TESTNET".to_string(), + seg_wit: "".to_string(), + chain_id: "".to_string(), + }; + + let mut keystore = Keystore::Hd(hd); + keystore.unlock_by_password(TEST_PASSWORD).unwrap(); + let acc = keystore + .derive_coin::(&coin) + .unwrap(); + assert_eq!( + "EQCKJfmBlnFiINcL1MoCjuyxULXaOEA-k5iHcr4L18RuhQHo", + acc.address + ); + } +} diff --git a/token-core/tcx-ton/src/signer.rs b/token-core/tcx-ton/src/signer.rs index 6f205fc9..ba967177 100644 --- a/token-core/tcx-ton/src/signer.rs +++ b/token-core/tcx-ton/src/signer.rs @@ -1,140 +1,36 @@ -use bitcoin::hashes::hex::ToHex; -use std::str::FromStr; -use std::sync::Arc; -use std::time::SystemTime; - -use num_bigint::BigUint; -use tonlib_core::cell::{BagOfCells, CellBuilder}; -use tonlib_core::message::{ - CommonMsgInfo, HasOpcode, JettonTransferMessage, TonMessage, TransferMessage, -}; -use tonlib_core::mnemonic::KeyPair; -use tonlib_core::wallet::{TonWallet, WalletVersion}; - +use tcx_common::sha256; use tcx_common::{FromHex, ToHex as OtherToHex}; use tcx_constants::Result; use tcx_keystore::{ Keystore, SignatureParameters, Signer, TransactionSigner as TraitTransactionSigner, }; -use tcx_primitive::{Ed25519PrivateKey, PrivateKey, PublicKey}; - -use crate::transaction::{TonTxIn, TonTxOut}; -impl TraitTransactionSigner for Keystore { - fn sign_transaction(&mut self, params: &SignatureParameters, tx: &TonTxIn) -> Result { - // for compare with other impl - let sec_key_bytes = - hex::decode("34815c96ad2434988d86a01e4b639acf41e8ecac7eeb260635b8a47028bbefd3") - .unwrap(); - let sec_key = Ed25519PrivateKey::from_slice(&sec_key_bytes).unwrap(); - let public_key = sec_key.public_key().to_bytes(); - let public_key_hex = public_key.to_hex(); - dbg!(public_key_hex); +use crate::transaction::{TonRawTxIn, TonTxOut}; - // construct a null key_pair - let null_key_pair = KeyPair { - public_key, - secret_key: vec![], - }; - - let wallet = TonWallet::derive_default(WalletVersion::V4R2, &null_key_pair)?; - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH)? - .as_secs() as u32; - let transfer = if tx.is_jetton { - let jetton_amount = BigUint::from_str(&tx.jetton_amount)?; - let from = tx.from.parse()?; - let to = tx.to.parse()?; - let amount = BigUint::from_str(&tx.amount)?; - let mut jetton_transfer = JettonTransferMessage::new(&to, &jetton_amount); - jetton_transfer.with_response_destination(&from); - jetton_transfer.set_query_id(tx.query_id); - let cell = jetton_transfer.build()?; +impl TraitTransactionSigner for Keystore { + fn sign_transaction( + &mut self, + params: &SignatureParameters, + tx: &TonRawTxIn, + ) -> Result { + let raw_data = Vec::from_hex_auto(&tx.raw_data)?; + let hash = sha256(&raw_data); - TransferMessage::new(CommonMsgInfo::new_default_internal(&to, &amount)) - .with_data(Arc::new(cell)) - .build()? - - // let wallet = TonWallet::derive_default(WalletVersion::V4R2, &null_key_pair)?; - // let now = SystemTime::now() - // .duration_since(SystemTime::UNIX_EPOCH)? - // .as_secs() as u32; - // let body = wallet.create_external_body(now + 60, tx.sequence_no.try_into().unwrap(), vec![transfer])?; - // let hash = body.cell_hash(); - // let sig = self.ed25519_sign(&hash.to_vec(), ¶ms.derivation_path)?; - // let mut body_builder = CellBuilder::new(); - // body_builder.store_slice(sig.as_slice())?; - // body_builder.store_cell(&body)?; - // let signed_body = body_builder.build()?; - // - // let wrapped_body = wallet.wrap_signed_body(signed_body, true)?; - // let boc = BagOfCells::from_root(wrapped_body); - // let tx = boc.serialize(true)?; - // let signature = tx.to_hex(); - // Ok(TonTxOut { - // signature, - // }) - } else { - let to = tx.to.parse()?; - let amount = BigUint::from_str(&tx.amount)?; - TransferMessage::new(CommonMsgInfo::new_default_internal(&to, &amount)) - // .with_data(Arc::new(cell)) - .build()? - }; - - let body = wallet.create_external_body( - now + 60, - tx.sequence_no.clone().try_into().unwrap(), - vec![Arc::new(transfer.clone())], - )?; - let hash = body.cell_hash(); let sig = self.ed25519_sign(&hash.to_vec(), ¶ms.derivation_path)?; - let mut body_builder = CellBuilder::new(); - body_builder.store_slice(sig.as_slice())?; - body_builder.store_cell(&body)?; - let signed_body = body_builder.build()?; - - let wrapped_body = wallet.wrap_signed_body(signed_body, true)?; - let boc = BagOfCells::from_root(wrapped_body); - let signed_tx = boc.serialize(true)?; - let signature = signed_tx.to_hex(); - dbg!(&signature); - - // for compare with other impl - let sec_key_bytes = - hex::decode("34815c96ad2434988d86a01e4b639acf41e8ecac7eeb260635b8a47028bbefd3") - .unwrap(); - let sec_key = Ed25519PrivateKey::from_slice(&sec_key_bytes).unwrap(); - let public_key = sec_key.public_key().to_bytes(); - let keypair = tonlib_core::mnemonic::KeyPair { - public_key, - secret_key: hex::decode("34815c96ad2434988d86a01e4b639acf41e8ecac7eeb260635b8a47028bbefd3eb1f50b92caa0063308b060c73d1393e504c1566e8092096466acb974af99bf0").unwrap() - }; - let wallet2 = TonWallet::derive_default(WalletVersion::V4R2, &keypair).unwrap(); - let body = wallet.create_external_body( - now + 60, - tx.sequence_no.try_into().unwrap(), - vec![Arc::new(transfer)], - )?; - let signed_body = wallet2.sign_external_body(&body).unwrap(); - let wrapped_body = wallet.wrap_signed_body(signed_body, true)?; - let boc = BagOfCells::from_root(wrapped_body); - let signed_tx = boc.serialize(true)?; - let signature = signed_tx.to_hex(); - dbg!(&signature); - - Ok(TonTxOut { signature }) + Ok(TonTxOut { + signature: sig.to_0x_hex(), + }) } } #[cfg(test)] mod test_super { - use crate::transaction::TonTxIn; use tcx_common::ToHex; use tcx_constants::{CoinInfo, CurveType, TEST_MNEMONIC, TEST_PASSWORD}; use tcx_keystore::{HdKeystore, Keystore, Metadata, SignatureParameters, TransactionSigner}; - use tonlib_core::wallet::WalletVersion; + + use crate::transaction::TonRawTxIn; #[test] fn test_nacl_sign() { @@ -166,23 +62,15 @@ mod test_super { "34815c96ad2434988d86a01e4b639acf41e8ecac7eeb260635b8a47028bbefd3" ); - let tx_in = TonTxIn { - from: "EQCKJfmBlnFiINcL1MoCjuyxULXaOEA-k5iHcr4L18RuhQHo".to_string(), - to: "0QBhzrMl_WXpLg6QQDVXAaCJAiCCDczkgmIxCfBejgH4RfFK".to_string(), - amount: "100000000000".to_string(), - memo: "".to_string(), - is_jetton: false, - jetton_amount: "20000000000".to_string(), - query_id: 30000, - sequence_no: 0, - wallet_version: "".to_string(), + let tx_in = TonRawTxIn { + raw_data: "".to_string(), }; let param = SignatureParameters { curve: CurveType::ED25519, derivation_path: path.to_string(), chain_type: "TON".to_string(), - network: "".to_string(), + network: "TESTNET".to_string(), seg_wit: "".to_string(), }; diff --git a/token-core/tcx-ton/src/transaction.rs b/token-core/tcx-ton/src/transaction.rs index fcf2eaf4..899995d1 100644 --- a/token-core/tcx-ton/src/transaction.rs +++ b/token-core/tcx-ton/src/transaction.rs @@ -10,25 +10,3 @@ pub struct TonTxOut { #[prost(string, tag = "1")] pub signature: ::prost::alloc::string::String, } -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct TonTxIn { - #[prost(string, tag = "1")] - pub from: ::prost::alloc::string::String, - #[prost(string, tag = "2")] - pub to: ::prost::alloc::string::String, - #[prost(string, tag = "3")] - pub amount: ::prost::alloc::string::String, - #[prost(string, tag = "4")] - pub memo: ::prost::alloc::string::String, - #[prost(bool, tag = "5")] - pub is_jetton: bool, - #[prost(string, tag = "6")] - pub jetton_amount: ::prost::alloc::string::String, - #[prost(uint64, tag = "7")] - pub query_id: u64, - #[prost(int32, tag = "8")] - pub sequence_no: i32, - #[prost(string, tag = "9")] - pub wallet_version: ::prost::alloc::string::String, -}