diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c98cc615..b077abf1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.17.0] – 2021-06-21 + +### New +- Added support of external encryption boxes. [See the documentation](docs/mod_crypto.md#register_encryption_box) +- **Debot module**: + - Dengine waits for completion of all transactions in a chain initiated by debot's onchain call. + ## [1.16.1] – 2021-06-16 ### New diff --git a/api/derive/Cargo.toml b/api/derive/Cargo.toml index 55b3f0da7..938f73e62 100644 --- a/api/derive/Cargo.toml +++ b/api/derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "api_derive" -version = "1.16.1" +version = "1.17.0" authors = ["TON DEV SOLUTIONS LTD "] edition = "2018" diff --git a/api/info/Cargo.toml b/api/info/Cargo.toml index 84f0a3b68..d87ff3187 100644 --- a/api/info/Cargo.toml +++ b/api/info/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "api_info" -version = "1.16.1" +version = "1.17.0" authors = ["TON DEV SOLUTIONS LTD "] edition = "2018" diff --git a/api/test/Cargo.toml b/api/test/Cargo.toml index 6017f59d4..c147c7799 100644 --- a/api/test/Cargo.toml +++ b/api/test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "api_test" -version = "1.16.1" +version = "1.17.0" authors = ["TON DEV SOLUTIONS LTD "] edition = "2018" diff --git a/docs/mod_crypto.md b/docs/mod_crypto.md index dcfc15cfd..cd36c74a1 100644 --- a/docs/mod_crypto.md +++ b/docs/mod_crypto.md @@ -80,11 +80,25 @@ Crypto functions. [remove_signing_box](#remove_signing_box) – Removes signing box from SDK. +[register_encryption_box](#register_encryption_box) – Register an application implemented encryption box. + +[remove_encryption_box](#remove_encryption_box) – Removes encryption box from SDK + +[encryption_box_get_info](#encryption_box_get_info) – Queries info from the given encryption box + +[encryption_box_encrypt](#encryption_box_encrypt) – Encrypts data using given encryption box + +[encryption_box_decrypt](#encryption_box_decrypt) – Decrypts data using given encryption box + ## Types [CryptoErrorCode](#CryptoErrorCode) [SigningBoxHandle](#SigningBoxHandle) +[EncryptionBoxHandle](#EncryptionBoxHandle) + +[EncryptionBoxInfo](#EncryptionBoxInfo) – Encryption box information + [ParamsOfFactorize](#ParamsOfFactorize) [ResultOfFactorize](#ResultOfFactorize) @@ -207,8 +221,28 @@ Crypto functions. [ResultOfSigningBoxSign](#ResultOfSigningBoxSign) +[RegisteredEncryptionBox](#RegisteredEncryptionBox) + +[ParamsOfAppEncryptionBox](#ParamsOfAppEncryptionBox) – Encryption box callbacks. + +[ResultOfAppEncryptionBox](#ResultOfAppEncryptionBox) – Returning values from signing box callbacks. + +[ParamsOfEncryptionBoxGetInfo](#ParamsOfEncryptionBoxGetInfo) + +[ResultOfEncryptionBoxGetInfo](#ResultOfEncryptionBoxGetInfo) + +[ParamsOfEncryptionBoxEncrypt](#ParamsOfEncryptionBoxEncrypt) + +[ResultOfEncryptionBoxEncrypt](#ResultOfEncryptionBoxEncrypt) + +[ParamsOfEncryptionBoxDecrypt](#ParamsOfEncryptionBoxDecrypt) + +[ResultOfEncryptionBoxDecrypt](#ResultOfEncryptionBoxDecrypt) + [AppSigningBox](#AppSigningBox) +[AppEncryptionBox](#AppEncryptionBox) + # Functions ## factorize @@ -1324,6 +1358,125 @@ function remove_signing_box( - `handle`: _[SigningBoxHandle](mod_crypto.md#SigningBoxHandle)_ – Handle of the signing box. +## register_encryption_box + +Register an application implemented encryption box. + +```ts +type RegisteredEncryptionBox = { + handle: EncryptionBoxHandle +} + +function register_encryption_box( + obj: AppEncryptionBox, +): Promise; +``` + + +### Result + +- `handle`: _[EncryptionBoxHandle](mod_crypto.md#EncryptionBoxHandle)_ – Handle of the encryption box + + +## remove_encryption_box + +Removes encryption box from SDK + +```ts +type RegisteredEncryptionBox = { + handle: EncryptionBoxHandle +} + +function remove_encryption_box( + params: RegisteredEncryptionBox, +): Promise; +``` +### Parameters +- `handle`: _[EncryptionBoxHandle](mod_crypto.md#EncryptionBoxHandle)_ – Handle of the encryption box + + +## encryption_box_get_info + +Queries info from the given encryption box + +```ts +type ParamsOfEncryptionBoxGetInfo = { + encryption_box: EncryptionBoxHandle +} + +type ResultOfEncryptionBoxGetInfo = { + info: EncryptionBoxInfo +} + +function encryption_box_get_info( + params: ParamsOfEncryptionBoxGetInfo, +): Promise; +``` +### Parameters +- `encryption_box`: _[EncryptionBoxHandle](mod_crypto.md#EncryptionBoxHandle)_ – Encryption box handle + + +### Result + +- `info`: _[EncryptionBoxInfo](mod_crypto.md#EncryptionBoxInfo)_ – Encryption box information + + +## encryption_box_encrypt + +Encrypts data using given encryption box + +```ts +type ParamsOfEncryptionBoxEncrypt = { + encryption_box: EncryptionBoxHandle, + data: string +} + +type ResultOfEncryptionBoxEncrypt = { + data: string +} + +function encryption_box_encrypt( + params: ParamsOfEncryptionBoxEncrypt, +): Promise; +``` +### Parameters +- `encryption_box`: _[EncryptionBoxHandle](mod_crypto.md#EncryptionBoxHandle)_ – Encryption box handle +- `data`: _string_ – Data to be encrypted, encoded in Base64 + + +### Result + +- `data`: _string_ – Encrypted data, encoded in Base64 + + +## encryption_box_decrypt + +Decrypts data using given encryption box + +```ts +type ParamsOfEncryptionBoxDecrypt = { + encryption_box: EncryptionBoxHandle, + data: string +} + +type ResultOfEncryptionBoxDecrypt = { + data: string +} + +function encryption_box_decrypt( + params: ParamsOfEncryptionBoxDecrypt, +): Promise; +``` +### Parameters +- `encryption_box`: _[EncryptionBoxHandle](mod_crypto.md#EncryptionBoxHandle)_ – Encryption box handle +- `data`: _string_ – Data to be decrypted, encoded in Base64 + + +### Result + +- `data`: _string_ – Decrypted data, encoded in Base64 + + # Types ## CryptoErrorCode ```ts @@ -1347,7 +1500,8 @@ enum CryptoErrorCode { MnemonicGenerationFailed = 119, MnemonicFromEntropyFailed = 120, SigningBoxNotRegistered = 121, - InvalidSignature = 122 + InvalidSignature = 122, + EncryptionBoxNotRegistered = 123 } ``` One of the following value: @@ -1372,6 +1526,7 @@ One of the following value: - `MnemonicFromEntropyFailed = 120` - `SigningBoxNotRegistered = 121` - `InvalidSignature = 122` +- `EncryptionBoxNotRegistered = 123` ## SigningBoxHandle @@ -1380,6 +1535,29 @@ type SigningBoxHandle = number ``` +## EncryptionBoxHandle +```ts +type EncryptionBoxHandle = number +``` + + +## EncryptionBoxInfo +Encryption box information + +```ts +type EncryptionBoxInfo = { + hdpath?: string, + algorithm?: string, + options?: any, + public?: any +} +``` +- `hdpath`?: _string_ – Derivation path, for instance "m/44'/396'/0'/0/0" +- `algorithm`?: _string_ – Cryptographic algorithm, used by this encryption box +- `options`?: _any_ – Options, depends on algorithm and specific encryption box implementation +- `public`?: _any_ – Public information, depends on algorithm + + ## ParamsOfFactorize ```ts type ParamsOfFactorize = { @@ -2080,6 +2258,164 @@ type ResultOfSigningBoxSign = {
Encoded with `hex`. +## RegisteredEncryptionBox +```ts +type RegisteredEncryptionBox = { + handle: EncryptionBoxHandle +} +``` +- `handle`: _[EncryptionBoxHandle](mod_crypto.md#EncryptionBoxHandle)_ – Handle of the encryption box + + +## ParamsOfAppEncryptionBox +Encryption box callbacks. + +```ts +type ParamsOfAppEncryptionBox = { + type: 'GetInfo' +} | { + type: 'Encrypt' + data: string +} | { + type: 'Decrypt' + data: string +} +``` +Depends on value of the `type` field. + +When _type_ is _'GetInfo'_ + +Get encryption box info + + +When _type_ is _'Encrypt'_ + +Encrypt data + + +- `data`: _string_ – Data, encoded in Base64 + +When _type_ is _'Decrypt'_ + +Decrypt data + + +- `data`: _string_ – Data, encoded in Base64 + + +Variant constructors: + +```ts +function paramsOfAppEncryptionBoxGetInfo(): ParamsOfAppEncryptionBox; +function paramsOfAppEncryptionBoxEncrypt(data: string): ParamsOfAppEncryptionBox; +function paramsOfAppEncryptionBoxDecrypt(data: string): ParamsOfAppEncryptionBox; +``` + +## ResultOfAppEncryptionBox +Returning values from signing box callbacks. + +```ts +type ResultOfAppEncryptionBox = { + type: 'GetInfo' + info: EncryptionBoxInfo +} | { + type: 'Encrypt' + data: string +} | { + type: 'Decrypt' + data: string +} +``` +Depends on value of the `type` field. + +When _type_ is _'GetInfo'_ + +Result of getting encryption box info + + +- `info`: _[EncryptionBoxInfo](mod_crypto.md#EncryptionBoxInfo)_ + +When _type_ is _'Encrypt'_ + +Result of encrypting data + + +- `data`: _string_ – Encrypted data, encoded in Base64 + +When _type_ is _'Decrypt'_ + +Result of decrypting data + + +- `data`: _string_ – Decrypted data, encoded in Base64 + + +Variant constructors: + +```ts +function resultOfAppEncryptionBoxGetInfo(info: EncryptionBoxInfo): ResultOfAppEncryptionBox; +function resultOfAppEncryptionBoxEncrypt(data: string): ResultOfAppEncryptionBox; +function resultOfAppEncryptionBoxDecrypt(data: string): ResultOfAppEncryptionBox; +``` + +## ParamsOfEncryptionBoxGetInfo +```ts +type ParamsOfEncryptionBoxGetInfo = { + encryption_box: EncryptionBoxHandle +} +``` +- `encryption_box`: _[EncryptionBoxHandle](mod_crypto.md#EncryptionBoxHandle)_ – Encryption box handle + + +## ResultOfEncryptionBoxGetInfo +```ts +type ResultOfEncryptionBoxGetInfo = { + info: EncryptionBoxInfo +} +``` +- `info`: _[EncryptionBoxInfo](mod_crypto.md#EncryptionBoxInfo)_ – Encryption box information + + +## ParamsOfEncryptionBoxEncrypt +```ts +type ParamsOfEncryptionBoxEncrypt = { + encryption_box: EncryptionBoxHandle, + data: string +} +``` +- `encryption_box`: _[EncryptionBoxHandle](mod_crypto.md#EncryptionBoxHandle)_ – Encryption box handle +- `data`: _string_ – Data to be encrypted, encoded in Base64 + + +## ResultOfEncryptionBoxEncrypt +```ts +type ResultOfEncryptionBoxEncrypt = { + data: string +} +``` +- `data`: _string_ – Encrypted data, encoded in Base64 + + +## ParamsOfEncryptionBoxDecrypt +```ts +type ParamsOfEncryptionBoxDecrypt = { + encryption_box: EncryptionBoxHandle, + data: string +} +``` +- `encryption_box`: _[EncryptionBoxHandle](mod_crypto.md#EncryptionBoxHandle)_ – Encryption box handle +- `data`: _string_ – Data to be decrypted, encoded in Base64 + + +## ResultOfEncryptionBoxDecrypt +```ts +type ResultOfEncryptionBoxDecrypt = { + data: string +} +``` +- `data`: _string_ – Decrypted data, encoded in Base64 + + ## AppSigningBox ```ts @@ -2146,3 +2482,104 @@ function sign( - `signature`: _string_ – Data signature encoded as hex +## AppEncryptionBox + +```ts + +type ResultOfAppEncryptionBoxGetInfo = { + info: EncryptionBoxInfo +} + +type ParamsOfAppEncryptionBoxEncrypt = { + data: string +} + +type ResultOfAppEncryptionBoxEncrypt = { + data: string +} + +type ParamsOfAppEncryptionBoxDecrypt = { + data: string +} + +type ResultOfAppEncryptionBoxDecrypt = { + data: string +} + +export interface AppEncryptionBox { + get_info(): Promise, + encrypt(params: ParamsOfAppEncryptionBoxEncrypt): Promise, + decrypt(params: ParamsOfAppEncryptionBoxDecrypt): Promise, +} +``` + +## get_info + +Get encryption box info + +```ts +type ResultOfAppEncryptionBoxGetInfo = { + info: EncryptionBoxInfo +} + +function get_info(): Promise; +``` + + +### Result + +- `info`: _[EncryptionBoxInfo](mod_crypto.md#EncryptionBoxInfo)_ + + +## encrypt + +Encrypt data + +```ts +type ParamsOfAppEncryptionBoxEncrypt = { + data: string +} + +type ResultOfAppEncryptionBoxEncrypt = { + data: string +} + +function encrypt( + params: ParamsOfAppEncryptionBoxEncrypt, +): Promise; +``` +### Parameters +- `data`: _string_ – Data, encoded in Base64 + + +### Result + +- `data`: _string_ – Encrypted data, encoded in Base64 + + +## decrypt + +Decrypt data + +```ts +type ParamsOfAppEncryptionBoxDecrypt = { + data: string +} + +type ResultOfAppEncryptionBoxDecrypt = { + data: string +} + +function decrypt( + params: ParamsOfAppEncryptionBoxDecrypt, +): Promise; +``` +### Parameters +- `data`: _string_ – Data, encoded in Base64 + + +### Result + +- `data`: _string_ – Decrypted data, encoded in Base64 + + diff --git a/docs/modules.md b/docs/modules.md index ddd6e3606..c0014fc61 100644 --- a/docs/modules.md +++ b/docs/modules.md @@ -99,6 +99,16 @@ Where: [remove_signing_box](mod_crypto.md#remove_signing_box) – Removes signing box from SDK. +[register_encryption_box](mod_crypto.md#register_encryption_box) – Register an application implemented encryption box. + +[remove_encryption_box](mod_crypto.md#remove_encryption_box) – Removes encryption box from SDK + +[encryption_box_get_info](mod_crypto.md#encryption_box_get_info) – Queries info from the given encryption box + +[encryption_box_encrypt](mod_crypto.md#encryption_box_encrypt) – Encrypts data using given encryption box + +[encryption_box_decrypt](mod_crypto.md#encryption_box_decrypt) – Decrypts data using given encryption box + ## [abi](mod_abi.md) – Provides message encoding and decoding according to the ABI specification. [encode_message_body](mod_abi.md#encode_message_body) – Encodes message body according to ABI function call. diff --git a/ton_client/Cargo.toml b/ton_client/Cargo.toml index 8f8650076..1cb3a5e9b 100644 --- a/ton_client/Cargo.toml +++ b/ton_client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ton_client" -version = "1.16.1" +version = "1.17.0" authors = ["TON DEV SOLUTIONS LTD "] edition = "2018" license = "Apache-2.0" diff --git a/ton_client/src/client/client.rs b/ton_client/src/client/client.rs index 90fa8db4f..7a8e38f24 100644 --- a/ton_client/src/client/client.rs +++ b/ton_client/src/client/client.rs @@ -23,7 +23,7 @@ use crate::error::ClientResult; use crate::abi::AbiConfig; use crate::boc::{BocConfig, cache::Bocs}; use crate::crypto::CryptoConfig; -use crate::crypto::boxes::SigningBox; +use crate::crypto::boxes::{SigningBox, EncryptionBox}; use crate::debot::DEngine; use crate::json_interface::request::Request; use crate::json_interface::interop::ResponseType; @@ -37,6 +37,7 @@ use super::wasm_client_env::ClientEnv; #[derive(Default)] pub struct Boxes { pub(crate) signing_boxes: LockfreeMap>, + pub(crate) encryption_boxes: LockfreeMap>, } pub struct NetworkContext { diff --git a/ton_client/src/crypto/boxes.rs b/ton_client/src/crypto/boxes.rs index dbd3af4b8..032a1868c 100644 --- a/ton_client/src/crypto/boxes.rs +++ b/ton_client/src/crypto/boxes.rs @@ -1,8 +1,13 @@ -use super::KeyPair; -use super::Error; +use std::sync::Arc; + +use lockfree::map::ReadGuard; +use serde_json::Value; + use crate::client::ClientContext; use crate::error::ClientResult; -use std::sync::Arc; + +use super::Error; +use super::KeyPair; #[derive(Serialize, Deserialize, Clone, Debug, ApiType, Default, PartialEq)] pub struct SigningBoxHandle(pub u32); @@ -147,3 +152,157 @@ pub fn remove_signing_box( context.boxes.signing_boxes.remove(¶ms.handle.0); Ok(()) } + + +#[derive(Serialize, Deserialize, Clone, Debug, ApiType, Default, PartialEq)] +pub struct EncryptionBoxHandle(pub u32); + +impl From for EncryptionBoxHandle { + fn from(handle: u32) -> Self { + Self(handle) + } +} + +/// Encryption box information +#[derive(Serialize, Deserialize, Clone, Debug, ApiType, Default, PartialEq)] +pub struct EncryptionBoxInfo { + /// Derivation path, for instance "m/44'/396'/0'/0/0" + pub hdpath: Option, + /// Cryptographic algorithm, used by this encryption box + pub algorithm: Option, + /// Options, depends on algorithm and specific encryption box implementation + pub options: Option, + /// Public information, depends on algorithm + pub public: Option, +} + +#[async_trait::async_trait] +pub trait EncryptionBox { + /// Gets encryption box information + async fn get_info(&self) -> ClientResult; + /// Encrypts data + async fn encrypt(&self, data: &String) -> ClientResult; + /// Decrypts data + async fn decrypt(&self, data: &String) -> ClientResult; +} + +#[derive(Serialize, Deserialize, Clone, Debug, ApiType, Default, PartialEq)] +pub struct RegisteredEncryptionBox { + /// Handle of the encryption box + pub handle: EncryptionBoxHandle, +} + +/// Registers an application implemented encryption box. +pub async fn register_encryption_box( + context: std::sync::Arc, + encryption_box: impl EncryptionBox + Send + Sync + 'static, +) -> ClientResult { + let id = context.get_next_id(); + context.boxes.encryption_boxes.insert(id, Box::new(encryption_box)); + + Ok(RegisteredEncryptionBox { + handle: EncryptionBoxHandle(id), + }) +} + +fn get_registered_encryption_box<'context>( + context: &'context Arc, + handle: &EncryptionBoxHandle +) -> ClientResult>> { + context.boxes.encryption_boxes + .get(&handle.0) + .ok_or(Error::encryption_box_not_registered(handle.0)) +} + +/// Removes encryption box from SDK +#[api_function] +pub fn remove_encryption_box( + context: Arc, + params: RegisteredEncryptionBox, +) -> ClientResult<()> { + context.boxes.encryption_boxes.remove(¶ms.handle.0); + Ok(()) +} + +#[derive(Serialize, Deserialize, Clone, Debug, ApiType, Default, PartialEq)] +pub struct ParamsOfEncryptionBoxGetInfo { + /// Encryption box handle + pub encryption_box: EncryptionBoxHandle, +} + +#[derive(Serialize, Deserialize, Clone, Debug, ApiType, Default, PartialEq)] +pub struct ResultOfEncryptionBoxGetInfo { + /// Encryption box information + pub info: EncryptionBoxInfo, +} + +/// Queries info from the given encryption box +#[api_function] +pub async fn encryption_box_get_info( + context: Arc, + params: ParamsOfEncryptionBoxGetInfo, +) -> ClientResult { + Ok(ResultOfEncryptionBoxGetInfo { + info: get_registered_encryption_box(&context, ¶ms.encryption_box)? + .val() + .get_info() + .await? + }) +} + +#[derive(Serialize, Deserialize, Clone, Debug, ApiType, Default, PartialEq)] +pub struct ParamsOfEncryptionBoxEncrypt { + /// Encryption box handle + pub encryption_box: EncryptionBoxHandle, + /// Data to be encrypted, encoded in Base64 + pub data: String, +} + + +#[derive(Serialize, Deserialize, Clone, Debug, ApiType, Default, PartialEq)] +pub struct ResultOfEncryptionBoxEncrypt { + /// Encrypted data, encoded in Base64 + pub data: String, +} + +/// Encrypts data using given encryption box +#[api_function] +pub async fn encryption_box_encrypt( + context: Arc, + params: ParamsOfEncryptionBoxEncrypt, +) -> ClientResult { + Ok(ResultOfEncryptionBoxEncrypt { + data: get_registered_encryption_box(&context, ¶ms.encryption_box)? + .val() + .encrypt(¶ms.data) + .await? + }) +} + +#[derive(Serialize, Deserialize, Clone, Debug, ApiType, Default, PartialEq)] +pub struct ParamsOfEncryptionBoxDecrypt { + /// Encryption box handle + pub encryption_box: EncryptionBoxHandle, + /// Data to be decrypted, encoded in Base64 + pub data: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, ApiType, Default, PartialEq)] +pub struct ResultOfEncryptionBoxDecrypt { + /// Decrypted data, encoded in Base64 + pub data: String, +} + +/// Decrypts data using given encryption box +#[api_function] +pub async fn encryption_box_decrypt( + context: Arc, + params: ParamsOfEncryptionBoxDecrypt, +) -> ClientResult { + Ok(ResultOfEncryptionBoxDecrypt { + data: get_registered_encryption_box(&context, ¶ms.encryption_box)? + .val() + .decrypt(¶ms.data) + .await? + }) +} diff --git a/ton_client/src/crypto/errors.rs b/ton_client/src/crypto/errors.rs index 9e6edeb24..b8800f6be 100644 --- a/ton_client/src/crypto/errors.rs +++ b/ton_client/src/crypto/errors.rs @@ -23,6 +23,7 @@ pub enum ErrorCode { MnemonicFromEntropyFailed = 120, SigningBoxNotRegistered = 121, InvalidSignature = 122, + EncryptionBoxNotRegistered = 123, } pub struct Error; @@ -168,4 +169,11 @@ impl Error { format!("Signing box is not registered. ID {}", id), ) } + + pub fn encryption_box_not_registered(id: u32) -> ClientError { + error( + ErrorCode::EncryptionBoxNotRegistered, + format!("Encryption box is not registered. ID {}", id), + ) + } } diff --git a/ton_client/src/crypto/mod.rs b/ton_client/src/crypto/mod.rs index 96da09fa3..48e78a07d 100644 --- a/ton_client/src/crypto/mod.rs +++ b/ton_client/src/crypto/mod.rs @@ -29,8 +29,14 @@ mod tests; pub use crate::crypto::boxes::{ get_signing_box, register_signing_box, remove_signing_box, signing_box_get_public_key, - signing_box_sign, ParamsOfSigningBoxSign, RegisteredSigningBox, ResultOfSigningBoxGetPublicKey, + signing_box_sign, register_encryption_box, remove_encryption_box, + encryption_box_get_info, encryption_box_encrypt, encryption_box_decrypt, + ParamsOfSigningBoxSign, RegisteredSigningBox, ResultOfSigningBoxGetPublicKey, ResultOfSigningBoxSign, SigningBox, SigningBoxHandle, + RegisteredEncryptionBox, EncryptionBoxHandle, EncryptionBoxInfo, + ParamsOfEncryptionBoxGetInfo, ResultOfEncryptionBoxGetInfo, + ParamsOfEncryptionBoxEncrypt, ResultOfEncryptionBoxEncrypt, + ParamsOfEncryptionBoxDecrypt, ResultOfEncryptionBoxDecrypt, }; pub use crate::crypto::encscrypt::{scrypt, ParamsOfScrypt, ResultOfScrypt}; pub use crate::crypto::hash::{sha256, sha512, ParamsOfHash, ResultOfHash}; diff --git a/ton_client/src/debot/calltype.rs b/ton_client/src/debot/calltype.rs index 3f5a3fefa..3c58ed0d0 100644 --- a/ton_client/src/debot/calltype.rs +++ b/ton_client/src/debot/calltype.rs @@ -3,7 +3,7 @@ use super::helpers::build_internal_message; use super::{BrowserCallbacks, DebotActivity, Spending, TonClient}; use crate::abi::Signer; use crate::boc::internal::{deserialize_object_from_base64, serialize_object_to_base64}; -use crate::boc::{parse_message, ParamsOfParse}; +use crate::boc::{get_boc_hash, parse_message, ParamsOfParse, ParamsOfGetBocHash}; use crate::crypto::{SigningBoxHandle}; use crate::encoding::decode_abi_number; use crate::error::{ClientError, ClientResult}; @@ -17,6 +17,7 @@ use std::fmt::Display; use std::sync::Arc; use ton_block::{Message, MsgAddressExt}; use ton_types::{BuilderData, Cell, IBitstring, SliceData}; +use crate::net::{query_transaction_tree, ParamsOfQueryTransactionTree}; const SUPPORTED_ABI_VERSION: u8 = 2; @@ -31,6 +32,7 @@ fn msg_err(e: impl Display) -> ClientError { Error::invalid_msg(e) } +#[derive(Default)] struct Metadata { answer_id: u32, onerror_id: u32, @@ -82,158 +84,282 @@ impl TryFrom for Metadata { } } -pub async fn run_get_method( +pub(crate) struct ContractCall { browser: Arc, ton: TonClient, - msg: String, + msg: Message, + signer: Signer, target_state: String, - debot_addr: &String, -) -> ClientResult { - let mut message: Message = deserialize_object_from_base64(&msg, "message") - .map_err(msg_err)? - .object; - let meta = get_meta(&mut message)?; - let (answer_id, _onerror_id, func_id, dest_addr, fixed_msg, _) = - decode_and_fix_ext_msg(ton.clone(), message, meta, Signer::None, browser, false) + debot_addr: String, + dest_addr: String, + local_run: bool, + meta: Metadata, +} + +impl ContractCall { + pub async fn new( + browser: Arc, + ton: TonClient, + msg: String, + signer: Signer, + target_state: String, + debot_addr: String, + local_run: bool, + ) -> ClientResult { + let mut msg: Message = deserialize_object_from_base64(&msg, "message") + .map_err(msg_err)? + .object; + let meta = get_meta(&mut msg)?; + let signer = resolve_signer( + !local_run, + signer, + meta.signing_box_handle.clone(), + browser.clone() + ).await?; + let dest_addr = msg + .header() + .get_dst_address() + .map(|x| x.to_string()) + .unwrap_or_default(); + Ok(Self { browser, ton, msg, signer, target_state, debot_addr, dest_addr, local_run, meta }) + } + + pub async fn execute(&self) -> ClientResult { + let result = self.decode_and_fix_ext_msg() .await - .map_err(|e| Error::get_method_failed(e))?; + .map_err(|e| Error::external_call_failed(e)); + if let Err(e) = result { + let error_body = build_onerror_body(self.meta.onerror_id, e)?; + return build_internal_message(&self.dest_addr, &self.debot_addr, error_body); + } - let mut result = run_tvm( - ton.clone(), - ParamsOfRunTvm { - account: target_state, - message: fixed_msg, - abi: None, - execution_options: None, - boc_cache: None, - return_updated_account: Some(true), - }, - ) - .await - .map_err(|e| Error::get_method_failed(e))?; + let (func_id, fixed_msg) = result.unwrap(); - if result.out_messages.len() != 1 { - return Err(Error::get_method_failed( - "get-metod returns more than 1 message", - )); + if self.local_run { + self.run_get_method(func_id, fixed_msg).await + } else { + self.send_ext_msg(func_id, fixed_msg).await + } } - let out_msg = result.out_messages.pop().unwrap(); - build_answer_msg(&out_msg, answer_id, func_id, &dest_addr, debot_addr) - .ok_or(Error::get_method_failed("failed to build answer message")) -} -pub async fn send_ext_msg<'a>( - browser: Arc, - ton: TonClient, - msg: String, - signer: Signer, - target_state: String, - debot_addr: &'a String, -) -> ClientResult { - let mut message: Message = deserialize_object_from_base64(&msg, "message") - .map_err(msg_err)? - .object; - let meta = get_meta(&mut message)?; - let onerror_id = meta.onerror_id; - let dest_addr = message - .header() - .get_dst_address() - .map(|x| x.to_string()) - .unwrap_or_default(); - - let result = decode_and_fix_ext_msg(ton.clone(), message, meta, signer, browser.clone(), true) + async fn run_get_method(&self, func_id: u32, fixed_msg: String) -> ClientResult { + let result = run_tvm( + self.ton.clone(), + ParamsOfRunTvm { + account: self.target_state.clone(), + message: fixed_msg, + abi: None, + execution_options: None, + boc_cache: None, + return_updated_account: Some(true), + }, + ) .await - .map_err(|e| Error::external_call_failed(e)); - if let Err(e) = result { - let error_body = build_onerror_body(onerror_id, e)?; - return build_internal_message(&dest_addr, debot_addr, error_body); + .map_err(|e| Error::get_method_failed(e)); + + if let Err(e) = result { + let error_body = build_onerror_body(self.meta.onerror_id, e)?; + return build_internal_message(&self.dest_addr, &self.debot_addr, error_body); + } + + let mut messages = result.unwrap().out_messages; + + if messages.len() != 1 { + return Err(Error::get_method_failed( + "get-method returns more than 1 message", + )); + } + let out_msg = messages.pop().unwrap(); + build_answer_msg(&out_msg, self.meta.answer_id, func_id, &self.dest_addr, &self.debot_addr) + .ok_or(Error::get_method_failed("failed to build answer message")) } + + async fn send_ext_msg(&self, func_id: u32, fixed_msg: String) -> ClientResult { + let activity = emulate_transaction( + self.ton.clone(), + self.dest_addr.clone(), + fixed_msg.clone(), + self.target_state.clone(), + self.signer.clone(), + ).await; + match activity { + Ok(activity) => { + if !self.browser.approve(activity).await? { + return self.build_error_answer_msg(Error::operation_rejected()); + } + }, + Err(e) => { + return self.build_error_answer_msg(e); + }, + } + + let browser = self.browser.clone(); + let callback = move |event| { + debug!("{:?}", event); + let browser = browser.clone(); + async move { + match event { + ProcessingEvent::WillSend { + shard_block_id: _, + message_id, + message: _, + } => { + browser.log(format!("Sending message {}", message_id)).await; + } + _ => (), + }; + } + }; - let (answer_id, onerror_id, func_id, dest_addr, fixed_msg, signer) = result.unwrap(); + let result = send_message( + self.ton.clone(), + ParamsOfSendMessage { + message: fixed_msg.clone(), + abi: None, + send_events: true, + }, + callback.clone(), + ) + .await + .map(|e| { error!("{:?}", e); e })?; - let activity = emulate_transaction( - ton.clone(), - dest_addr.clone(), - fixed_msg.clone(), - target_state, - signer, - ).await; - match activity { - Ok(activity) => { - if !browser.approve(activity).await? { - let error_body = build_onerror_body(onerror_id, Error::operation_rejected())?; - return build_internal_message(&dest_addr, debot_addr, error_body); + let result = wait_for_transaction( + self.ton.clone(), + ParamsOfWaitForTransaction { + abi: None, + message: fixed_msg.clone(), + shard_block_id: result.shard_block_id, + send_events: true, + sending_endpoints: Some(result.sending_endpoints), + }, + callback, + ) + .await; + match result { + Ok(res) => { + let msg_id = get_boc_hash(self.ton.clone(), ParamsOfGetBocHash { boc: fixed_msg }).await?.hash; + let result = query_transaction_tree( + self.ton.clone(), + ParamsOfQueryTransactionTree { + in_msg: msg_id, + ..Default::default() + }, + ).await; + if let Err(e) = result { + return self.build_error_answer_msg(e); + } + for out_msg in &res.out_messages { + let res = build_answer_msg( + out_msg, + self.meta.answer_id, + func_id, + &self.dest_addr, + &self.debot_addr + ); + if let Some(answer_msg) = res { + return Ok(answer_msg); + } + debug!("Skip outbound message"); + } + debug!("Build empty body"); + // answer message not found, build empty answer. + let mut new_body = BuilderData::new(); + new_body.append_u32(self.meta.answer_id).map_err(msg_err)?; + build_internal_message(&self.dest_addr, &self.debot_addr, new_body.into()) } - }, - Err(e) => { - let error_body = build_onerror_body(onerror_id, e)?; - return build_internal_message(&dest_addr, debot_addr, error_body); - }, + Err(e) => { + debug!("Transaction failed: {:?}", e); + self.build_error_answer_msg(e) + } + } } - let browser = browser.clone(); - let callback = move |event| { - debug!("{:?}", event); - let browser = browser.clone(); - async move { - match event { - ProcessingEvent::WillSend { - shard_block_id: _, - message_id, - message: _, - } => { - browser.log(format!("Sending message {}", message_id)).await; - } - _ => (), - }; + async fn decode_and_fix_ext_msg(&self) -> ClientResult<(u32, String)> { + // find function id in message body: parse signature, pubkey and abi headers + let mut message = self.msg.clone(); + let mut in_body_slice = message.body().ok_or(msg_err("empty body"))?; + // skip signature bit and signature if present + let sign_bit = in_body_slice.get_next_bit().map_err(msg_err)?; + if let Signer::SigningBox { handle: _ } = self.signer { + if !sign_bit { + return Err(msg_err("signature bit is zero")); + } + in_body_slice.get_next_bits(512).map_err(msg_err)?; } - }; - let result = send_message( - ton.clone(), - ParamsOfSendMessage { - message: fixed_msg.clone(), - abi: None, - send_events: true, - }, - callback.clone(), - ) - .await - .map(|e| { - error!("{:?}", e); - e - })?; - let result = wait_for_transaction( - ton.clone(), - ParamsOfWaitForTransaction { - abi: None, - message: fixed_msg, - shard_block_id: result.shard_block_id, - send_events: true, - sending_endpoints: Some(result.sending_endpoints), - }, - callback, - ) - .await; - match result { - Ok(res) => { - for out_msg in &res.out_messages { - let res = build_answer_msg(out_msg, answer_id, func_id, &dest_addr, debot_addr); - if let Some(answer_msg) = res { - return Ok(answer_msg); - } - debug!("Skip outbound message"); + if self.meta.is_pubkey { + let pubkey_bit = in_body_slice.get_next_bit().map_err(msg_err)?; + if pubkey_bit { + in_body_slice.get_next_bits(256).map_err(msg_err)?; } - debug!("Build empty body"); - // answer message not found, build empty answer. - let mut new_body = BuilderData::new(); - new_body.append_u32(answer_id).map_err(msg_err)?; - build_internal_message(&dest_addr, debot_addr, new_body.into()) } - Err(e) => { - debug!("Transaction failed: {:?}", e); - let error_body = build_onerror_body(onerror_id, e)?; - build_internal_message(&dest_addr, debot_addr, error_body) + if self.meta.is_timestamp { + // skip `timestamp` header + in_body_slice.get_next_u64().map_err(msg_err)?; } + if self.meta.is_expire { + // skip `expire` header + in_body_slice.get_next_u32().map_err(msg_err)?; + } + // remember function id + let func_id = in_body_slice.get_next_u32().map_err(msg_err)?; + + // rebuild msg body - insert correct `timestamp` and `expire` headers if they are present, + // then sign body with signing box + + let mut new_body = BuilderData::new(); + let pubkey = self.signer.resolve_public_key(self.ton.clone()).await?; + if self.meta.is_pubkey { + if let Some(ref key) = pubkey { + new_body + .append_bit_one() + .and_then(|b| b.append_raw(&hex::decode(key).unwrap(), 256)) + .map_err(msg_err)?; + } else { + // pubkey bit = 0 + new_body.append_bit_zero().map_err(msg_err)?; + } + } + let now = self.ton.env.now_ms(); + let expired_at = ((now / 1000) as u32) + self.ton.config.abi.message_expiration_timeout; + if self.meta.is_timestamp { + new_body.append_u64(now).map_err(msg_err)?; + } + if self.meta.is_expire { + new_body.append_u32(expired_at).map_err(msg_err)?; + } + new_body + .append_u32(func_id) + .and_then(|b| b.append_builder(&BuilderData::from_slice(&in_body_slice))) + .map_err(msg_err)?; + + let mut signed_body = BuilderData::new(); + match self.signer { + Signer::SigningBox { handle: _ } => { + let hash = Cell::from(&new_body).repr_hash().as_slice().to_vec(); + let signature = self.signer.sign(self.ton.clone(), &hash).await?; + if let Some(signature) = signature { + signed_body + .append_bit_one() + .and_then(|b| b.append_raw(&signature, signature.len() * 8)) + .map_err(msg_err)?; + } else { + signed_body.append_bit_zero().map_err(msg_err)?; + } + }, + _ => { + signed_body.append_bit_zero().map_err(msg_err)?; + } + } + signed_body.append_builder(&new_body).map_err(msg_err)?; + + message.set_body(signed_body.into()); + let msg = serialize_object_to_base64(&message, "message").map_err(|e| Error::invalid_msg(e))?; + Ok((func_id, msg)) + } + + fn build_error_answer_msg(&self, e: ClientError) -> ClientResult { + let error_body = build_onerror_body(self.meta.onerror_id, e)?; + build_internal_message(&self.dest_addr, &self.debot_addr, error_body) } } @@ -251,102 +377,6 @@ fn build_onerror_body(onerror_id: u32, e: ClientError) -> ClientResult, - sign: bool, -) -> ClientResult<(u32, u32, u32, String, String, Signer)> { - let signer = resolve_signer(sign, signer, meta.signing_box_handle, browser.clone()).await?; - // find function id in message body: parse signature, pubkey and abi headers - - let mut in_body_slice = message.body().ok_or(msg_err("empty body"))?; - // skip signature bit and signature if present - let sign_bit = in_body_slice.get_next_bit().map_err(msg_err)?; - if let Signer::SigningBox { handle: _ } = signer { - if !sign_bit { - return Err(msg_err("signature bit is zero")); - } - in_body_slice.get_next_bits(512).map_err(msg_err)?; - } - if meta.is_pubkey { - let pubkey_bit = in_body_slice.get_next_bit().map_err(msg_err)?; - if pubkey_bit { - in_body_slice.get_next_bits(256).map_err(msg_err)?; - } - } - if meta.is_timestamp { - // skip `timestamp` header - in_body_slice.get_next_u64().map_err(msg_err)?; - } - if meta.is_expire { - // skip `expire` header - in_body_slice.get_next_u32().map_err(msg_err)?; - } - // remember function id - let func_id = in_body_slice.get_next_u32().map_err(msg_err)?; - - // rebuild msg body - insert correct `timestamp` and `expire` headers if they are present, - // then sign body with signing box - - let mut new_body = BuilderData::new(); - let pubkey = signer.resolve_public_key(ton.clone()).await?; - if meta.is_pubkey { - if let Some(ref key) = pubkey { - new_body - .append_bit_one() - .and_then(|b| b.append_raw(&hex::decode(key).unwrap(), 256)) - .map_err(msg_err)?; - } else { - // pubkey bit = 0 - new_body.append_bit_zero().map_err(msg_err)?; - } - } - let now = ton.env.now_ms(); - let expired_at = ((now / 1000) as u32) + ton.config.abi.message_expiration_timeout; - if meta.is_timestamp { - new_body.append_u64(now).map_err(msg_err)?; - } - if meta.is_expire { - new_body.append_u32(expired_at).map_err(msg_err)?; - } - new_body - .append_u32(func_id) - .and_then(|b| b.append_builder(&BuilderData::from_slice(&in_body_slice))) - .map_err(msg_err)?; - - let mut signed_body = BuilderData::new(); - match signer { - Signer::SigningBox { handle: _ } => { - let hash = Cell::from(&new_body).repr_hash().as_slice().to_vec(); - let signature = signer.sign(ton.clone(), &hash).await?; - if let Some(signature) = signature { - signed_body - .append_bit_one() - .and_then(|b| b.append_raw(&signature, signature.len() * 8)) - .map_err(msg_err)?; - } else { - signed_body.append_bit_zero().map_err(msg_err)?; - } - } - _ => { - signed_body.append_bit_zero().map_err(msg_err)?; - } - } - signed_body.append_builder(&new_body).map_err(msg_err)?; - - message.set_body(signed_body.into()); - let msg = serialize_object_to_base64(&message, "message").map_err(|e| Error::invalid_msg(e))?; - let dst = message - .header() - .get_dst_address() - .map(|x| x.to_string()) - .unwrap_or_default(); - Ok((meta.answer_id, meta.onerror_id, func_id, dst, msg, signer)) -} - fn build_answer_msg( out_msg: &String, answer_id: u32, diff --git a/ton_client/src/debot/dengine.rs b/ton_client/src/debot/dengine.rs index 648f016bb..586efd1d9 100644 --- a/ton_client/src/debot/dengine.rs +++ b/ton_client/src/debot/dengine.rs @@ -5,8 +5,7 @@ use super::context::{ }; use super::debot_abi::DEBOT_ABI; use super::errors::Error; -use super::calltype; -use super::calltype::DebotCallType; +use super::calltype::{ContractCall, DebotCallType}; use super::routines; use super::run_output::RunOutput; use super::{JsonValue, TonClient, DInfo}; @@ -832,31 +831,32 @@ impl DEngine { debug!("GetMethod call"); let target_state = Self::load_state(self.ton.clone(), dest.clone()).await .map_err(|e| Error::execute_failed(e))?; - let answer_msg = calltype::run_get_method( + let callobj = ContractCall::new( self.browser.clone(), self.ton.clone(), msg, + Signer::None, target_state, - &self.addr, - ) - .await?; - debug!("GetMethod succeeded"); + self.addr.clone(), + true, + ).await?; + let answer_msg = callobj.execute().await?; output.append(self.send_to_debot(answer_msg).await?); }, DebotCallType::External{msg, dest} => { debug!("External call"); let target_state = Self::load_state(self.ton.clone(), dest.clone()).await .map_err(|e| Error::execute_failed(e))?; - let answer_msg = calltype::send_ext_msg( + let callobj = ContractCall::new( self.browser.clone(), self.ton.clone(), msg, Signer::None, target_state, - &self.addr, - ) - .await?; - debug!("External call succeeded"); + self.addr.clone(), + false, + ).await?; + let answer_msg = callobj.execute().await?; output.append(self.send_to_debot(answer_msg).await?); }, DebotCallType::Invoke{msg} => { diff --git a/ton_client/src/debot/msg_interface.rs b/ton_client/src/debot/msg_interface.rs index 248ff7575..2e94685d5 100644 --- a/ton_client/src/debot/msg_interface.rs +++ b/ton_client/src/debot/msg_interface.rs @@ -1,4 +1,4 @@ -use super::calltype::send_ext_msg; +use super::calltype::{ContractCall}; use super::dinterface::{get_arg, DebotInterface, InterfaceResult}; use crate::abi::{decode_message, Abi, ParamsOfDecodeMessage}; use crate::crypto::{get_signing_box, KeyPair}; @@ -79,21 +79,24 @@ impl MsgInterface { let target_state = DEngine::load_state(self.ton.clone(), dest) .await .map_err(|e| format!("{}", e))?; - let msg = send_ext_msg( + let callobj = ContractCall::new( self.browser.clone(), self.ton.clone(), message, Signer::SigningBox{handle: signing_box}, target_state, - &self.debot_addr, - ) - .await - .map_err(|e| format!("{}", e))?; + self.debot_addr.clone(), + false, + ).await.map_err(|e| format!("{}", e))?; + let answer_msg = callobj.execute() + .await + .map_err(|e| format!("{}", e))?; + let result = decode_message( self.ton.clone(), ParamsOfDecodeMessage { abi: self.debot_abi.clone(), - message: msg, + message: answer_msg, }, ) .await diff --git a/ton_client/src/debot/tests.rs b/ton_client/src/debot/tests.rs index f48f209a0..bcf5de60b 100644 --- a/ton_client/src/debot/tests.rs +++ b/ton_client/src/debot/tests.rs @@ -1508,6 +1508,39 @@ async fn test_debot_network_interface() { ).await; } +#[tokio::test(core_threads = 2)] +async fn test_debot_transaction_chain() { + let client = std::sync::Arc::new(TestClient::new()); + let DebotData { debot_addr, target_addr: _, keys, abi } = init_simple_debot(client.clone(), "testDebot9").await; + let steps = serde_json::from_value(json!([])).unwrap(); + TestBrowser::execute_with_details( + client.clone(), + debot_addr.clone(), + keys, + steps, + vec![format!("Test passed")], + build_info(abi, 9, vec!["0x8796536366ee21852db56dccb60bc564598b618c865fc50c8b1ab740bba128e3".to_owned()]), + vec![], + ).await; +} + +fn build_info(abi: String, n: u32, interfaces: Vec) -> DebotInfo { + let name = format!("TestDeBot{}", n); + DebotInfo { + name: Some(name.clone()), + version: Some("0.1.0".to_owned()), + publisher: Some("TON Labs".to_owned()), + caption: Some(name.clone()), + author: Some("TON Labs".to_owned()), + support: Some("0:0000000000000000000000000000000000000000000000000000000000000000".to_owned()), + hello: Some(name.clone()), + language: Some("en".to_owned()), + dabi: Some(abi), + icon: Some(format!("")), + interfaces, + } +} + async fn download_account(client: &Arc, addr: &str) -> Option { let client = client.clone(); let accounts: ResultOfQueryCollection = client.request_async( diff --git a/ton_client/src/json_interface/crypto.rs b/ton_client/src/json_interface/crypto.rs index 24629e14f..2bc911db7 100644 --- a/ton_client/src/json_interface/crypto.rs +++ b/ton_client/src/json_interface/crypto.rs @@ -12,9 +12,10 @@ * */ - use crate::client::{AppObject, ClientContext, Error}; - use crate::error::ClientResult; - use crate::crypto::{RegisteredSigningBox, SigningBox}; +use crate::client::{AppObject, ClientContext, Error}; +use crate::error::ClientResult; +use crate::crypto::{RegisteredSigningBox, SigningBox}; +use crate::crypto::boxes::{EncryptionBoxInfo, EncryptionBox, RegisteredEncryptionBox}; /// Signing box callbacks. #[derive(Serialize, Deserialize, Clone, Debug, ApiType, PartialEq)] @@ -84,7 +85,7 @@ impl SigningBox for ExternalSigningBox { } } - /// Register an application implemented signing box. +/// Register an application implemented signing box. #[api_function] pub(crate) async fn register_signing_box( context: std::sync::Arc, @@ -92,3 +93,95 @@ pub(crate) async fn register_signing_box( ) -> ClientResult { crate::crypto::register_signing_box(context, ExternalSigningBox::new(app_object)).await } + +/// Encryption box callbacks. +#[derive(Serialize, Deserialize, Clone, Debug, ApiType, PartialEq)] +#[serde(tag="type")] +pub enum ParamsOfAppEncryptionBox { + /// Get encryption box info + GetInfo, + /// Encrypt data + Encrypt { + /// Data, encoded in Base64 + data: String, + }, + /// Decrypt data + Decrypt { + /// Data, encoded in Base64 + data: String, + } +} + +/// Returning values from signing box callbacks. +#[derive(Serialize, Deserialize, Clone, Debug, ApiType, PartialEq)] +#[serde(tag="type")] +pub enum ResultOfAppEncryptionBox { + /// Result of getting encryption box info + GetInfo { + info: EncryptionBoxInfo, + }, + /// Result of encrypting data + Encrypt { + /// Encrypted data, encoded in Base64 + data: String, + }, + /// Result of decrypting data + Decrypt { + /// Decrypted data, encoded in Base64 + data: String, + }, +} + +struct ExternalEncryptionBox { + app_object: AppObject, +} + +impl ExternalEncryptionBox { + pub fn new(app_object: AppObject) -> Self { + Self { app_object } + } +} + +#[async_trait::async_trait] +impl EncryptionBox for ExternalEncryptionBox { + async fn get_info(&self) -> ClientResult { + let response = self.app_object.call(ParamsOfAppEncryptionBox::GetInfo).await?; + + match response { + ResultOfAppEncryptionBox::GetInfo { info } => Ok(info), + _ => Err(Error::unexpected_callback_response( + "EncryptionBoxGetInfo", &response)) + } + } + + async fn encrypt(&self, data: &String) -> ClientResult { + let response = + self.app_object.call(ParamsOfAppEncryptionBox::Encrypt { data: data.clone() }).await?; + + match response { + ResultOfAppEncryptionBox::Encrypt { data } => Ok(data), + _ => Err(Error::unexpected_callback_response( + "EncryptionBoxEncrypt", &response)) + } + } + + async fn decrypt(&self, data: &String) -> ClientResult { + let response = + self.app_object.call(ParamsOfAppEncryptionBox::Decrypt { data: data.clone() }).await?; + + match response { + ResultOfAppEncryptionBox::Decrypt { data } => Ok(data), + _ => Err(Error::unexpected_callback_response( + "EncryptionBoxDecrypt", &response)) + } + } +} + +/// Register an application implemented encryption box. +#[api_function] +pub(crate) async fn register_encryption_box( + context: std::sync::Arc, + app_object: AppObject, +) -> ClientResult { + crate::crypto::register_encryption_box(context, ExternalEncryptionBox::new(app_object)).await +} diff --git a/ton_client/src/json_interface/modules.rs b/ton_client/src/json_interface/modules.rs index 78dea253b..610e43e93 100644 --- a/ton_client/src/json_interface/modules.rs +++ b/ton_client/src/json_interface/modules.rs @@ -57,6 +57,8 @@ fn register_crypto(handlers: &mut RuntimeHandlers) { module.register_error_code::(); module.register_type::(); + module.register_type::(); + module.register_type::(); // Math @@ -197,6 +199,7 @@ fn register_crypto(handlers: &mut RuntimeHandlers) { // Boxes + // Signing box module.register_async_fn_with_app_object_no_args( super::crypto::register_signing_box, super::crypto::register_signing_box_api, @@ -218,6 +221,28 @@ fn register_crypto(handlers: &mut RuntimeHandlers) { crate::crypto::boxes::remove_signing_box_api, ); + // Encryption box + module.register_async_fn_with_app_object_no_args( + super::crypto::register_encryption_box, + super::crypto::register_encryption_box_api, + ); + module.register_sync_fn( + crate::crypto::remove_encryption_box, + crate::crypto::boxes::remove_encryption_box_api, + ); + module.register_async_fn( + crate::crypto::encryption_box_get_info, + crate::crypto::boxes::encryption_box_get_info_api, + ); + module.register_async_fn( + crate::crypto::encryption_box_encrypt, + crate::crypto::boxes::encryption_box_encrypt_api, + ); + module.register_async_fn( + crate::crypto::encryption_box_decrypt, + crate::crypto::boxes::encryption_box_decrypt_api, + ); + module.register(); } diff --git a/ton_client/src/tests/contracts/abi_v2/testDebot9.abi.json b/ton_client/src/tests/contracts/abi_v2/testDebot9.abi.json new file mode 100644 index 000000000..fda85d719 --- /dev/null +++ b/ton_client/src/tests/contracts/abi_v2/testDebot9.abi.json @@ -0,0 +1,142 @@ +{ + "ABI version": 2, + "header": ["pubkey", "time", "expire"], + "functions": [ + { + "name": "getseqno", + "inputs": [ + ], + "outputs": [ + {"name":"seqno","type":"uint256"} + ] + }, + { + "name": "getcompleted", + "inputs": [ + ], + "outputs": [ + {"name":"completed","type":"bool"} + ] + }, + { + "name": "start", + "inputs": [ + ], + "outputs": [ + ] + }, + { + "name": "getDebotInfo", + "id": "0xDEB", + "inputs": [ + ], + "outputs": [ + {"name":"name","type":"bytes"}, + {"name":"version","type":"bytes"}, + {"name":"publisher","type":"bytes"}, + {"name":"caption","type":"bytes"}, + {"name":"author","type":"bytes"}, + {"name":"support","type":"address"}, + {"name":"hello","type":"bytes"}, + {"name":"language","type":"bytes"}, + {"name":"dabi","type":"bytes"}, + {"name":"icon","type":"bytes"} + ] + }, + { + "name": "getRequiredInterfaces", + "inputs": [ + ], + "outputs": [ + {"name":"interfaces","type":"uint256[]"} + ] + }, + { + "name": "onSuccess", + "inputs": [ + {"name":"n","type":"uint256"} + ], + "outputs": [ + ] + }, + { + "name": "onError", + "inputs": [ + {"name":"sdkError","type":"uint32"}, + {"name":"exitCode","type":"uint32"} + ], + "outputs": [ + ] + }, + { + "name": "onGetSeqno", + "inputs": [ + {"name":"seqno","type":"uint256"} + ], + "outputs": [ + ] + }, + { + "name": "onCompleted", + "inputs": [ + {"name":"completed","type":"bool"} + ], + "outputs": [ + ] + }, + { + "name": "startChain", + "inputs": [ + ], + "outputs": [ + {"name":"n","type":"uint256"} + ] + }, + { + "name": "deployNext", + "inputs": [ + {"name":"initNo","type":"uint256"} + ], + "outputs": [ + ] + }, + { + "name": "completed", + "inputs": [ + ], + "outputs": [ + ] + }, + { + "name": "getDebotOptions", + "inputs": [ + ], + "outputs": [ + {"name":"options","type":"uint8"}, + {"name":"debotAbi","type":"bytes"}, + {"name":"targetAbi","type":"bytes"}, + {"name":"targetAddr","type":"address"} + ] + }, + { + "name": "setABI", + "inputs": [ + {"name":"dabi","type":"bytes"} + ], + "outputs": [ + ] + }, + { + "name": "constructor", + "inputs": [ + ], + "outputs": [ + ] + } + ], + "data": [ + {"key":1,"name":"_seqno","type":"uint256"} + ], + "events": [ + ] +} diff --git a/ton_client/src/tests/contracts/abi_v2/testDebot9.sol b/ton_client/src/tests/contracts/abi_v2/testDebot9.sol new file mode 100644 index 000000000..01256064e --- /dev/null +++ b/ton_client/src/tests/contracts/abi_v2/testDebot9.sol @@ -0,0 +1,143 @@ +pragma ton-solidity >=0.43.0; +pragma AbiHeader expire; +pragma AbiHeader time; +pragma AbiHeader pubkey; +import "../Debot.sol"; +import "../Terminal.sol"; + +// +// ------- ------ ----- +// startChain -> | 1 | deployNext -> | 2 | -> ... | N | -- +// ------- ------ ----- | +// ^--- completed ----- +contract TestDebot9 is Debot { + uint256 constant CHAIN_LENGTH = 10; + uint256 static _seqno; + bool _completed; + address _sender; + + function getseqno() public view returns(uint256 seqno) { + seqno = _seqno; + } + + function getcompleted() public view returns(bool completed) { + completed = _completed; + } + + /// @notice Entry point function for DeBot. + function start() public override { + optional(uint256) key = tvm.pubkey(); + TestDebot9(address(this)).startChain{ + abiVer: 2, + extMsg: true, + sign: true, + time: 0, + expire: 0, + pubkey: key, + callbackId: tvm.functionId(onSuccess), + onErrorId: tvm.functionId(onError) + }(); + + } + + /// @notice Returns Metadata about DeBot. + function getDebotInfo() public functionID(0xDEB) override view returns( + string name, string version, string publisher, string caption, string author, + address support, string hello, string language, string dabi, bytes icon + ) { + name = "TestDeBot9"; + version = "0.1.0"; + publisher = "TON Labs"; + caption = "TestDeBot9"; + author = "TON Labs"; + support = address(0); + hello = "TestDeBot9"; + language = "en"; + dabi = m_debotAbi.get(); + icon = ""; + } + + function getRequiredInterfaces() public view override returns (uint256[] interfaces) { + return [ Terminal.ID ]; + } + + function onSuccess(uint256 n) public view { + require(n == _seqno, 101); + optional(uint256) none; + address addr = address(tvm.hash(tvm.buildStateInit({ + code: tvm.code(), + pubkey: tvm.pubkey(), + varInit: {_seqno: _seqno + CHAIN_LENGTH}, + contr: TestDebot9 + }))); + TestDebot9(addr).getseqno{ + abiVer: 2, + extMsg: true, + sign: false, + time: 0, + expire: 0, + pubkey: none, + callbackId: tvm.functionId(onGetSeqno), + onErrorId: tvm.functionId(onError) + }(); + } + + function onError(uint32 sdkError, uint32 exitCode) public pure { + sdkError; exitCode; + revert(201); + } + + function onGetSeqno(uint256 seqno) public view { + require(seqno == _seqno + CHAIN_LENGTH, 301); + optional(uint256) none; + TestDebot9(address(this)).getcompleted{ + abiVer: 2, + extMsg: true, + sign: false, + time: 0, + expire: 0, + pubkey: none, + callbackId: tvm.functionId(onCompleted), + onErrorId: tvm.functionId(onError) + }(); + } + + function onCompleted(bool completed) public { + require(completed, 401); + Terminal.print(0, "Test passed"); + } + // + // Onchain functions + // + + function startChain() public returns (uint256 n) { + tvm.accept(); + _seqno = 0; + deployNext(_seqno); + return _seqno; + } + + function deployNext(uint256 initNo) public { + _sender = msg.sender; + if (initNo + CHAIN_LENGTH == _seqno) { + TestDebot9(_sender).completed{value: 0, flag: 64}(); + return; + } + + address addr = new TestDebot9 { + value: 0.1 ton, + flag: 1, + code: tvm.code(), + pubkey: tvm.pubkey(), + varInit: {_seqno: _seqno + 1} + }(); + TestDebot9(addr).deployNext{value: address(this).balance - 1 ton}(initNo); + } + + function completed() public { + _completed = true; + if (_sender != address(0)) { + TestDebot9(_sender).completed{value: 0, flag: 64}(); + } + } +} \ No newline at end of file diff --git a/ton_client/src/tests/contracts/abi_v2/testDebot9.tvc b/ton_client/src/tests/contracts/abi_v2/testDebot9.tvc new file mode 100644 index 000000000..d4843a4a2 Binary files /dev/null and b/ton_client/src/tests/contracts/abi_v2/testDebot9.tvc differ diff --git a/ton_sdk/Cargo.toml b/ton_sdk/Cargo.toml index feffc554b..70a7a8ed9 100644 --- a/ton_sdk/Cargo.toml +++ b/ton_sdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ton_sdk" -version = "1.16.1" +version = "1.17.0" edition = "2018" license = "Apache-2.0" authors = ["TON DEV SOLUTIONS LTD "] diff --git a/toncli/Cargo.toml b/toncli/Cargo.toml index 288cd863e..5316e6038 100644 --- a/toncli/Cargo.toml +++ b/toncli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "toncli" -version = "1.16.1" +version = "1.17.0" description = "TON CLient Command Line Tool" authors = ["TON DEV SOLUTIONS LTD "] repository = "https://github.com/tonlabs/TON-SDK" diff --git a/tools/api.json b/tools/api.json index 356c0dfcc..3678abc0e 100644 --- a/tools/api.json +++ b/tools/api.json @@ -1,5 +1,5 @@ { - "version": "1.16.1", + "version": "1.17.0", "modules": [ { "name": "client", @@ -1009,6 +1009,13 @@ "value": "122", "summary": null, "description": null + }, + { + "name": "EncryptionBoxNotRegistered", + "type": "Number", + "value": "123", + "summary": null, + "description": null } ], "summary": null, @@ -1022,6 +1029,60 @@ "summary": null, "description": null }, + { + "name": "EncryptionBoxHandle", + "type": "Number", + "number_type": "UInt", + "number_size": 32, + "summary": null, + "description": null + }, + { + "name": "EncryptionBoxInfo", + "type": "Struct", + "struct_fields": [ + { + "name": "hdpath", + "type": "Optional", + "optional_inner": { + "type": "String" + }, + "summary": "Derivation path, for instance \"m/44'/396'/0'/0/0\"", + "description": null + }, + { + "name": "algorithm", + "type": "Optional", + "optional_inner": { + "type": "String" + }, + "summary": "Cryptographic algorithm, used by this encryption box", + "description": null + }, + { + "name": "options", + "type": "Optional", + "optional_inner": { + "type": "Ref", + "ref_name": "Value" + }, + "summary": "Options, depends on algorithm and specific encryption box implementation", + "description": null + }, + { + "name": "public", + "type": "Optional", + "optional_inner": { + "type": "Ref", + "ref_name": "Value" + }, + "summary": "Public information, depends on algorithm", + "description": null + } + ], + "summary": "Encryption box information", + "description": null + }, { "name": "ParamsOfFactorize", "type": "Struct", @@ -2236,6 +2297,215 @@ ], "summary": null, "description": null + }, + { + "name": "RegisteredEncryptionBox", + "type": "Struct", + "struct_fields": [ + { + "name": "handle", + "type": "Ref", + "ref_name": "crypto.EncryptionBoxHandle", + "summary": "Handle of the encryption box", + "description": null + } + ], + "summary": null, + "description": null + }, + { + "name": "ParamsOfAppEncryptionBox", + "type": "EnumOfTypes", + "enum_types": [ + { + "name": "GetInfo", + "type": "Struct", + "struct_fields": [], + "summary": "Get encryption box info", + "description": null + }, + { + "name": "Encrypt", + "type": "Struct", + "struct_fields": [ + { + "name": "data", + "type": "String", + "summary": "Data, encoded in Base64", + "description": null + } + ], + "summary": "Encrypt data", + "description": null + }, + { + "name": "Decrypt", + "type": "Struct", + "struct_fields": [ + { + "name": "data", + "type": "String", + "summary": "Data, encoded in Base64", + "description": null + } + ], + "summary": "Decrypt data", + "description": null + } + ], + "summary": "Encryption box callbacks.", + "description": null + }, + { + "name": "ResultOfAppEncryptionBox", + "type": "EnumOfTypes", + "enum_types": [ + { + "name": "GetInfo", + "type": "Struct", + "struct_fields": [ + { + "name": "info", + "type": "Ref", + "ref_name": "crypto.EncryptionBoxInfo", + "summary": null, + "description": null + } + ], + "summary": "Result of getting encryption box info", + "description": null + }, + { + "name": "Encrypt", + "type": "Struct", + "struct_fields": [ + { + "name": "data", + "type": "String", + "summary": "Encrypted data, encoded in Base64", + "description": null + } + ], + "summary": "Result of encrypting data", + "description": null + }, + { + "name": "Decrypt", + "type": "Struct", + "struct_fields": [ + { + "name": "data", + "type": "String", + "summary": "Decrypted data, encoded in Base64", + "description": null + } + ], + "summary": "Result of decrypting data", + "description": null + } + ], + "summary": "Returning values from signing box callbacks.", + "description": null + }, + { + "name": "ParamsOfEncryptionBoxGetInfo", + "type": "Struct", + "struct_fields": [ + { + "name": "encryption_box", + "type": "Ref", + "ref_name": "crypto.EncryptionBoxHandle", + "summary": "Encryption box handle", + "description": null + } + ], + "summary": null, + "description": null + }, + { + "name": "ResultOfEncryptionBoxGetInfo", + "type": "Struct", + "struct_fields": [ + { + "name": "info", + "type": "Ref", + "ref_name": "crypto.EncryptionBoxInfo", + "summary": "Encryption box information", + "description": null + } + ], + "summary": null, + "description": null + }, + { + "name": "ParamsOfEncryptionBoxEncrypt", + "type": "Struct", + "struct_fields": [ + { + "name": "encryption_box", + "type": "Ref", + "ref_name": "crypto.EncryptionBoxHandle", + "summary": "Encryption box handle", + "description": null + }, + { + "name": "data", + "type": "String", + "summary": "Data to be encrypted, encoded in Base64", + "description": null + } + ], + "summary": null, + "description": null + }, + { + "name": "ResultOfEncryptionBoxEncrypt", + "type": "Struct", + "struct_fields": [ + { + "name": "data", + "type": "String", + "summary": "Encrypted data, encoded in Base64", + "description": null + } + ], + "summary": null, + "description": null + }, + { + "name": "ParamsOfEncryptionBoxDecrypt", + "type": "Struct", + "struct_fields": [ + { + "name": "encryption_box", + "type": "Ref", + "ref_name": "crypto.EncryptionBoxHandle", + "summary": "Encryption box handle", + "description": null + }, + { + "name": "data", + "type": "String", + "summary": "Data to be decrypted, encoded in Base64", + "description": null + } + ], + "summary": null, + "description": null + }, + { + "name": "ResultOfEncryptionBoxDecrypt", + "type": "Struct", + "struct_fields": [ + { + "name": "data", + "type": "String", + "summary": "Decrypted data, encoded in Base64", + "description": null + } + ], + "summary": null, + "description": null } ], "functions": [ @@ -3677,6 +3947,205 @@ ] }, "errors": null + }, + { + "name": "register_encryption_box", + "summary": "Register an application implemented encryption box.", + "description": null, + "params": [ + { + "name": "context", + "type": "Generic", + "generic_name": "Arc", + "generic_args": [ + { + "type": "Ref", + "ref_name": "ClientContext" + } + ], + "summary": null, + "description": null + }, + { + "name": "app_object", + "type": "Generic", + "generic_name": "AppObject", + "generic_args": [ + { + "type": "Ref", + "ref_name": "crypto.ParamsOfAppEncryptionBox" + }, + { + "type": "Ref", + "ref_name": "crypto.ResultOfAppEncryptionBox" + } + ], + "summary": null, + "description": null + } + ], + "result": { + "type": "Generic", + "generic_name": "ClientResult", + "generic_args": [ + { + "type": "Ref", + "ref_name": "crypto.RegisteredEncryptionBox" + } + ] + }, + "errors": null + }, + { + "name": "remove_encryption_box", + "summary": "Removes encryption box from SDK", + "description": null, + "params": [ + { + "name": "context", + "type": "Generic", + "generic_name": "Arc", + "generic_args": [ + { + "type": "Ref", + "ref_name": "ClientContext" + } + ], + "summary": null, + "description": null + }, + { + "name": "params", + "type": "Ref", + "ref_name": "crypto.RegisteredEncryptionBox", + "summary": null, + "description": null + } + ], + "result": { + "type": "Generic", + "generic_name": "ClientResult", + "generic_args": [ + { + "type": "None" + } + ] + }, + "errors": null + }, + { + "name": "encryption_box_get_info", + "summary": "Queries info from the given encryption box", + "description": null, + "params": [ + { + "name": "context", + "type": "Generic", + "generic_name": "Arc", + "generic_args": [ + { + "type": "Ref", + "ref_name": "ClientContext" + } + ], + "summary": null, + "description": null + }, + { + "name": "params", + "type": "Ref", + "ref_name": "crypto.ParamsOfEncryptionBoxGetInfo", + "summary": null, + "description": null + } + ], + "result": { + "type": "Generic", + "generic_name": "ClientResult", + "generic_args": [ + { + "type": "Ref", + "ref_name": "crypto.ResultOfEncryptionBoxGetInfo" + } + ] + }, + "errors": null + }, + { + "name": "encryption_box_encrypt", + "summary": "Encrypts data using given encryption box", + "description": null, + "params": [ + { + "name": "context", + "type": "Generic", + "generic_name": "Arc", + "generic_args": [ + { + "type": "Ref", + "ref_name": "ClientContext" + } + ], + "summary": null, + "description": null + }, + { + "name": "params", + "type": "Ref", + "ref_name": "crypto.ParamsOfEncryptionBoxEncrypt", + "summary": null, + "description": null + } + ], + "result": { + "type": "Generic", + "generic_name": "ClientResult", + "generic_args": [ + { + "type": "Ref", + "ref_name": "crypto.ResultOfEncryptionBoxEncrypt" + } + ] + }, + "errors": null + }, + { + "name": "encryption_box_decrypt", + "summary": "Decrypts data using given encryption box", + "description": null, + "params": [ + { + "name": "context", + "type": "Generic", + "generic_name": "Arc", + "generic_args": [ + { + "type": "Ref", + "ref_name": "ClientContext" + } + ], + "summary": null, + "description": null + }, + { + "name": "params", + "type": "Ref", + "ref_name": "crypto.ParamsOfEncryptionBoxDecrypt", + "summary": null, + "description": null + } + ], + "result": { + "type": "Generic", + "generic_name": "ClientResult", + "generic_args": [ + { + "type": "Ref", + "ref_name": "crypto.ResultOfEncryptionBoxDecrypt" + } + ] + }, + "errors": null } ] },