TAPP 可信應用是運行在可信執行環境中,持有密鑰的 WebAssembly
智能合約。TAPP 提供了一套基于 C99/C++14 標準的 C++ 語言子集作為合約語言。TAPP 開發者在開發可信計算應用程序時,需要先下載、安裝 TAPP 編譯工具 mytf.mycdt
, 通過編譯工具將編寫的 TAPP 代碼編譯成 WASM 字節碼。之后,將TAPP 字節碼安裝到 MYTF 可信計算引擎中,由 MYTF 對 WASM 字節碼進行解釋并執行。
編寫 TAPP
TAPP 代碼基本框架如下(sample.cpp):
// 基本庫,包含了全部MYTF庫函數
#include <mychainlib/contract.h>
// 三方庫,本示例引入JSON庫
#include <third_party/rapidjson/document.h>
#include <third_party/rapidjson/stringbuffer.h>
#include <third_party/rapidjson/writer.h>
// 基本MYTF庫函數的命名空間
using namespace mychain;
// TAPP主類,與C++類定義相同,區別在于繼承Contract
class MyTapp : public Contract {
public:
// TAPP函數,與C++函數定義相同。區別在于,如果是外部可調用接口,使用INTERFACE宏
INTERFACE std::string Encode(const std::string& data) {
std::string out;
// 調用MYTF庫函數提供的接口(API):Base64Encode
CryptoErrorCode err = Base64Encode(data, out);
if (err != CryptoErrorCode::kSuccess) {
// 目前打印至MYTF日志
print("failed to base64encode: %d", err);
return std::to_string(err);
}
return out;
}
// TAPP函數,與C++函數定義相同。區別在于,如果是外部可調用接口,使用INTERFACE宏
INTERFACE int Add(int a, int b) {
return a + b;
}
};
// 通過宏定義外部可調用函數
INTERFACE_EXPORT(MyTapp, (Encode) (Add))
下載編譯工具
目前,編譯工具支持 Linux、macOS 系統,版本信息及對應下載地址如下:
名稱 | 版本 | 下載地址 |
mytf.mycdt | 0.2.2.1 |
該編譯工具鏈基于 Clang。開發者可以通過 mytf.mycdt
將可信計算應用代碼編譯成 WASM 字節碼,然后用 MYTF SDK 將 TAPP 安裝到 C3S MYTF 中。MYTF 可信計算引擎會對 WASM 字節碼進行解釋執行。
安裝編譯工具
下載編譯工具后,將工具包解壓到指定目錄,如 $HOME/mycdt
,并設置 PATH 環境變量。
注意:my++ --version
取決于安裝的版本。
$ export PATH=$PATH:$HOME/mycdt/bin
$ my++ --version
my++ version mytf-0.2.2.1
編譯應用
下面以可信應用 sample.cpp
為例編譯應用。
$ my++ sample.cpp -o sample.wasm
編譯完成后,生成 WASM 字節碼 sample.wasm
。可以通過 MYTF SDK 將該字節碼 安裝至 MYTF 可信計算引擎中進行解釋執行,具體操作參見 SDK 使用指南。
API 詳解
除 C++ 標準庫之外,MYTF 提供了若干庫函數和 API 接口,可以在 TAPP 中使用,提高易用性和性能。API 分類介紹如下:
基礎設施接口
MyAssert & Require
// 函數原型
// 如果(not expression),則終止程序,將msg信息寫入Output中
// (每次tapp調用會得到return_val和Output,其中output是系統級別信息,詳見SDK文檔)
// (以下不再贅述Output)
void MyAssert(int expression, const std::string& msg);
// 如果(not condition),則終止程序,將exception信息寫入Output中
// 返回值為0,無意義
int Require(bool condition, const std::string& exception);
// 兩個接口自Mychain智能合約就存在,差異不大
// 使用示例
INTERFACE std::string Encode(const std::string& data) {
std::string out;
// 調用MYTF庫函數提供的接口(API):Base64Encode
CryptoErrorCode err = Base64Encode(data, out);
MyAssert(err == CryptoErrorCode::kSuccess, "failed to base64encode");
return out;
}
LOG_*
包括 LOG_DEBUG
、LOG_INFO
和 LOG_ERROR
。打印調試信息。每次輸出信息作為一行,如 LOG_DEBUG("%d", INT32_MAX)
;將會輸出 “[DEBUG] 2147483647\n”。每行最大長度為512,超出此長度則會進行截斷。
// 原型
// 格式與 C99 中 printf 相仿
int LOG_DEBUG(const char* format, ...);
// 示例
INTERFACE void TestPrint() {
LOG_DEBUG("%d", INT32_MAX); // 2147483647
LOG_DEBUG("%d", -1); // -1
LOG_DEBUG("%u", -1); // 4294967295
LOG_DEBUG("%o", 16); // 20
LOG_DEBUG("%x", 16); // 10
LOG_DEBUG("%f", -1.01); // -1.010000
LOG_DEBUG("%c", 'a'); // a
LOG_INFO("%p", 0); // 00000000
LOG_INFO("%s", "hello"); // hello
LOG_INFO("%%"); // %
LOG_ERROR("hello"); // hello
LOG_ERROR(""); //
}
一次 tapp 調用期間,LOG
信息會保存在一個 rotate log buffer
內,調用結束之后將返回給調用者(ExecuteRes.log)。log buffer
大小目前設定為200k, 超出限制也會滾動寫入。如通過 LOG_DEBUG
寫入30k 行,每行20 個字節,則只有最后 10k 行會保留下來,返回給調用者。tapp 安裝時可以指定日志級別:
enum TappLogLevel {
kTappLogDebug = 0,
kTappLogInfo = 1,
kTappLogError = 2,
kTappLogDisable = 3,
};
默認為kTappLogDisable
,即不輸出任何日志。低于設定日志級別的 LOG
操作將不會生效。如安裝 tapp 時指定日志級別為 kTappLogError
,則 tapp 中的 LOG_DEBUG
和 LOG_INFO
將不會打印信息。
Bin2Hex & Hex2Bin
// 函數原型
// 每個字節轉化為兩個字節(hex模式)。如"\0\255" => "00ff" 或 "00FF" (uppercase = true)
std::string Bin2Hex(const std::string& input, bool uppercase = false);
// 上述函數的逆操作,允許0x或0X開頭
std::string Hex2Bin(const std::string& input);
信封接口
EnvelopeOpen & EnvelopeBuild
TAPP 內部可以使用該函數進行基于 ECIES-SECP256K1
或國密算法的加密解密。支持多方用戶數據加密后,由第三方服務平臺融合多方加密數據并請求執行 TAPP,服務平臺無法獲取用戶隱私數據。
// 函數原型
// 解密信封envelope,得到其中的plain_data。信封構造參考SDK文檔
CryptoErrorCode EnvelopeOpen(const std::string& envelope, std::string& plain_data);
// 構造信封,使用pk使用algo算法對plain_data進行加密,得到加密后的信封
CryptoErrorCode EnvelopeBuild(const std::string& pk, const std::string& plain_data, std::string& output_envelope, KEY_ALGO_TYPE algo = KEY_ALGO_TYPE::ECIES_SECP256K1_KEY);
// 實例
INTERFACE std::string TestEnvelopeBuild(std::string plain_data, std::string pk) {
print("plain_data %s are ready to be encrypt", plain_data.c_str());
std::string ret_envelope = "";
CryptoErrorCode ret = EnvelopeBuild(pk, plain_data, ret_envelope);
if(CryptoErrorCode::kSuccess != ret) {
print("Failed to encrypt envelope");
return std::to_string(ret);
}
return ret_envelope;
}
INTERFACE std::string TestEnvelopeOpen(std::string envelope_data) {
std::string plain_data = "";
CryptoErrorCode ret = EnvelopeOpen(envelope_data, plain_data);
if(CryptoErrorCode::kSuccess != ret) {
print("Failed to decrypt envelope");
return std::to_string(ret);
}
print("Decrypted data: %s", plain_data.c_str());
return plain_data;
}
ECElgamalEnvelopeDecrypt
TAPP 內部可以使用該函數進行基于 ECElgamal-SECP256K1
算法的解密。支持數據一方加密后多方解密。
// 原型
// 指定curve_type與prikey,對cipher解密得到output
CryptoErrorCode ECElgamalEnvelopeOpen(uint32_t curve_type, const std::string& prikey, const std::string& cipher, std::string& output);
// 示例
INTERFACE std::string TestECElgamalEnvelopeDecrypt(uint32_t pk_num, std::string prikey, std::string cipher_data) {
uint32_t curve_type = 0;
std::string plain_data = "";
CryptoErrorCode ret = ECElgamalEnvelopeOpen(curve_type, prikey, cipher_data, plain_data);
if (CryptoErrorCode::kSuccess != ret) {
print("Failed to decrypt envelope");
return std::to_string(ret);
}
return plain_data;
}
AuthEnvelopeDecrypt
在 MYTF 中,每個計算 TAPP 擁有加密公私鑰。數據所有者需要使用 TAPP 的公鑰加密,對隱私構造 AuthEnvelope
數據權屬信封結構,對數據的使用權限進行把控,保證了只有該 TAPP 才能解密此數據。當數據所有者需要將隱私數據使用權授信給其他人時,需要簽發 VC 給數據使用者,明確誰可以使用此數據執行 TAPP 的哪些函數。當 TAPP 收到數據使用者的計算請求時,會校驗使用者是否具有隱私數據的使用權限。
// 原型
// 數據擁有者的加密數據envelope,調用者提供擁有者的授權authorization和自身身份證明id_proof。調用此接口得到原文數據plain
CryptoErrorCode AuthEnvelopeOpen(const std::string& envelope, const std::string& authorization, const std::string& id_proof, std::string& plain /* out */);
// 示例
INTERFACE std::string TestAuthEnvelopeDecrypt(std::string envelope_data, std::string auth, std::string id_proof) {
std::string plain_data = "";
CryptoErrorCode res_code = AuthEnvelopeOpen(envelope_data, auth, id_proof, plain_data);
if (CryptoErrorCode::kSuccess != res_code) {
print("Failed to decrypt authenvelope: %u", res_code);
return std::to_string(res_code);
}
print("Decrypted auth data: %s", plain_data.c_str());
return plain_data;
}
密碼接口
RsaSign & RsaVerify
// 原型
// 使用pri_key,采用digest_name摘要方式,對data簽名得到sign
CryptoErrorCode RsaSign(const std::string& pri_key, const std::string& digest_name, const std::string& data, std::string& sign);
// 使用pub_key,采用digest_name摘要方式,驗證data的簽名sign
CryptoErrorCode RsaVerify(const std::string& pub_key, const std::string& digest_name, const std::string& data, const std::string& sign);
// 示例
INTERFACE std::vector<std::string> OrderVerify() {
std::vector<std::string> ret;
// 密碼配置
const std::string digest_name = "SHA1";
const std::string pri_key = "-----BEGIN RSA PRIVATE KEY-----\nXXBase64XX\n-----END RSA PRIVATE KEY-----";
const std::string pub_key = "XXBase64XX";
// 獲取簽名
std::string encode_params = "some data";
std::string sign;
CryptoErrorCode sign_err = RsaSign(pri_key, digest_name, encode_params, sign);
if (sign_err != CryptoErrorCode::kSuccess) {
print("failed to sign: %u", sign_err);
ret.push_back("FAIL"); ret.push_back("failed to sign"); ret.push_back(std::to_string(sign_err));
return ret;
}
std::string encode_resp = "some data";
std::string resp_sign = "XXBase64XX";
// 驗證簽名
CryptoErrorCode verify_err = RsaVerify(pub_key, digest_name, encode_resp, resp_sign);
if (verify_err != CryptoErrorCode::kSuccess) {
print("failed to verify sig: %u", verify_err);
ret.push_back("FAIL"); ret.push_back("failed to verify signature"); ret.push_back(std::to_string(verify_err));
}
return ret;
}
TappEcdsaSign
TAPP函數:使用TAPP的私鑰對 data
進行簽名,簽名算法為 SECP256K1
,內部實現使用bitcoin優化過的 secp256k1
算法。驗簽端注意對此算法對齊。
函數原型
/*
功能:使用當前 TAPP 的簽名私鑰,對 data 進行簽名。摘要算法為 SHA256,簽名算法曲線為 curve_name,默認為 SECP256K1
返回值:執行結果,int 型枚舉類型。具體錯誤碼解釋見后文
參數:
data: 入參,需要簽名的數據
sign: 出參,得到簽名的結果。
curve_name: 缺省入參,簽名算法使用的曲線,可選 ECDSA_RAW_SECP256K1_KEY或ECDSA_SM2P256V1_KEY
*/
CryptoErrorCode TappEcdsaSign(const std::string& data, std::string& sign, KEY_ALGO_TYPE curve_name = KEY_ALGO_TYPE::ECDSA_RAW_SECP256K1_KEY);
Sha256
// 原型
// 計算msg的SHA256摘要,得到hash
CryptoErrorCode Sha256(const std::string& msg, std::string& hash);
Hmac
/* 原型
* 計算Hmac, 使用md_type摘要算法,使用key對msg計算hmac,得到結果mac
* 其中摘要算法類型
enum MdType: int {
kSha1 = 0,
kSha256 = 1,
};
*/
CryptoErrorCode Hmac(const MdType& md_type, const std::string& msg, const std::string& key, std::string& mac);
// 示例
String hmac_key = "abcd";
String hmac_data = "hello";
String hmac_out;
CryptoErrorCode ret = Hmac(MdType::kSha1, hmac_data, hmac_key, hmac_out);
MyAssert(CryptoErrorCode::kSuccess == ret, "hmac errcode");
MyAssert(Bin2Hex(hmac_out) == "5126823fdb3f4ee3f970f8274929a50bbd5c8d0c", "hmac result");
國密接口
支持 Sm3_256
、Sm2Sm3Sign
、Sm2Sm3Verify
、Sm2Sm3Encrypt
、Sm2Sm3Decrypt
、Sm4GcmEncrypt
、Sm4GcmDecrypt
,示例如下:
// 函數原型
// const參數msg為入參,非const參數hash為結果出參。使用SM3 256算法,對msg計算摘要hash
// 如無特殊說明,以下const參數均為入參,非const參數均為結果出參。函數和參數名清晰,不再贅述具體含義
CryptoErrorCode Sm3_256(const std::string& msg, std::string& hash);
CryptoErrorCode Sm2Sm3Sign(const std::string& msg, const std::string& prikey, std::string& signature);
CryptoErrorCode Sm2Sm3Verify(const std::string& msg, const std::string& pubkey, const std::string& signature);
CryptoErrorCode Sm2Sm3Encrypt(const std::string& plain, const std::string& pubkey, std::string& cipher);
CryptoErrorCode Sm2Sm3Decrypt(const std::string& cipher, const std::string& prikey, std::string& plain);
CryptoErrorCode Sm4GcmEncrypt(const std::string& plain, const std::string& secret_key, std::string& cipher);
CryptoErrorCode Sm4GcmDecrypt(const std::string& cipher, const std::string& secret_key, std::string& plain);
// 示例
// 綜合測試SM所有接口
INTERFACE int TestSM(String plain, String expected_hash, String pubkey, String prikey, String secret_key) {
// Sm3 hash
String hash;
CryptoErrorCode ret = Sm3_256(plain, hash);
MyAssert(CryptoErrorCode::kSuccess == ret, "sm3 256");
MyAssert(hash == expected_hash, "test sm3_256");
// Sm2Sm3 sign
String signature;
ret = Sm2Sm3Sign(plain, prikey, signature);
MyAssert(CryptoErrorCode::kSuccess == ret, "sm2 sm3 sign");
ret = Sm2Sm3Verify(plain, pubkey, signature);
MyAssert(CryptoErrorCode::kSuccess == ret, "sm2 sm3 verify");
// Sm2Sm3 encrypt
String cipher;
ret = Sm2Sm3Encrypt(plain, pubkey, cipher);
MyAssert(CryptoErrorCode::kSuccess == ret, "sm2 sm3 encrypt");
String plain_decrypted;
ret = Sm2Sm3Decrypt(cipher, prikey, plain_decrypted);
MyAssert(CryptoErrorCode::kSuccess == ret, "sm2 sm3 decrypt");
MyAssert(plain == plain_decrypted, "test sm2 sm3 encrypt");
// Sm4 encrypt
ret = Sm4GcmEncrypt(plain, secret_key, cipher);
MyAssert(CryptoErrorCode::kSuccess == ret, "sm4 gcm encrypt");
ret = Sm4GcmDecrypt(cipher, secret_key, plain_decrypted);
MyAssert(CryptoErrorCode::kSuccess == ret, "sm4 gcm decrypt");
MyAssert(plain == plain_decrypted, "test sm4 gcm encrypt");
return 0;
}
對稱加解密接口
支持 AesGcmDecrypt
(”AES/GCM/NoPadding”解密算法,支持密鑰長度128/192/256 bit)、AesEcbDecrypt
(”AES/ECB/PKCS5Padding”解密算法,支持密鑰長度128/192/256 bit)、AesGcmEncrypt
(”AES/GCM/NoPadding”加密算法,支持密鑰長度128/192/256 bit)、AesEcbEncrypt
(”AES/ECB/PKCS5Padding”加密算法,支持密鑰長度128/192/256 bit),參考代碼見下:
/**
函數原型:AesGcmDecrypt
參數說明:
key: 加密密鑰,合法長度16/24/32字節
iv: Initialization_Vector, 12字節
aad: Additional Authenticated Data
cipher: 密文
plain[out]: 解密后明文
返回值:
CryptoErrorCode 錯誤碼
*/
CryptoErrorCode AesGcmDecrypt(const std::string& key, const std::string& iv, const std::string& aad, const std::string& cipher, std::string& plain);
// 代碼示例
INTERFACE String TestAesGcmDecrypt(String key, String iv, String aad, String cipher) {
String out;
CryptoErrorCode err = AesGcmDecrypt(key, iv, aad, cipher, out);
if (err != CryptoErrorCode::kSuccess) {
print("failed to AesGcmDecrypt: %d", err);
return std::to_string(err);
}
print("TestAesGcmDecrypt, out size %d, %s", out.size(), out.c_str());
return out;
}
/**
函數原型:AesGcmEncrypt
參數說明:
key: 加密密鑰,合法長度16/24/32字節
iv: Initialization_Vector, 12字節
aad: Additional Authenticated Data
plain: 明文
cipher[out]: 加密后密文
返回值:
CryptoErrorCode 錯誤碼
*/
CryptoErrorCode AesGcmEncrypt(const std::string& key, const std::string& iv, const std::string& aad, const std::string& plain, std::string& cipher);
// 示例代碼
INTERFACE String TestAesGcmEncrypt(String key, String iv, String aad, String plain) {
String out;
CryptoErrorCode err = AesGcmEncrypt(key, iv, aad, plain, out);
if (err != CryptoErrorCode::kSuccess) {
print("failed to AesGcmEncrypt: %d", err);
return std::to_string(err);
}
print("TestAesGcmEncrypt, out size %d, %s", out.size(), out.c_str());
return out;
}
/**
函數原型:AesEcbDecrypt
參數說明:
key: 加密密鑰,合法長度16/24/32字節
cipher: 密文
plain[out]: 解密后明文
返回值:
CryptoErrorCode 錯誤碼
*/
CryptoErrorCode AesEcbDecrypt(const std::string& key, const std::string& cipher, std::string& plain);
// 示例代碼
INTERFACE String TestAesEcbDecrypt(String key, String cipher) {
String out;
CryptoErrorCode err = AesEcbDecrypt(key, cipher, out);
if (err != CryptoErrorCode::kSuccess) {
print("failed to AesEcbDecrypt: %d", err);
return std::to_string(err);
}
print("TestAesEcbDecrypt, out %s", out.c_str());
return out;
}
/**
函數原型:AesEcbEncrypt
參數說明:
key: 加密密鑰,合法長度16/24/32字節
plain: 明文
cipher: 加密后密文
返回值:
CryptoErrorCode 錯誤碼
*/
CryptoErrorCode AesEcbEncrypt(const std::string& key, const std::string& plain, std::string& cipher);
// 示例代碼
INTERFACE String TestAesEcbEncrypt(String key, String plain) {
String out;
CryptoErrorCode err = AesEcbEncrypt(key, plain, out);
if (err != CryptoErrorCode::kSuccess) {
print("failed to AesEcbEncrypt: %d", err);
return std::to_string(err);
}
print("TestAesEcbEncrypt, out %s", out.c_str());
return out;
}
Tools
Base64Encode
、Base64Decode
的示例如下:
// 原型
CryptoErrorCode Base64Encode(const std::string& input, std::string& output);
CryptoErrorCode Base64Decode(const std::string& input, std::string& output);
時間處理接口
SDK版本要求:0.2.2.2+。
時間字符串轉UNIX時間戳:
strptime
說明要引用 <time.h> 頭文件
INTERFACE long StrptimeEval(String time_string, String fmt, int utc_offset) {
struct tm tm;
memset(&tm, 0, sizeof(struct tm));
// char *strptime_tz(const char *restrict s, const char *restrict f, int utc_hour_off, struct tm *restrict tm)
// 按給定時區(UTC偏移)將本地時間字符串轉換為本地時間tm結構
// 目前支持時區:CHINA_STANDARD_TIME_TZ_OFFSET
// GREENWICH_MEAN_TIME_TZ_OFFSET
// 或者 直接提供本地時區與UTC之間差距
char* s = strptime_tz(time_string.c_str(), fmt.c_str(), utc_offset, &tm);
MyAssert(s != NULL, "strptime");
// 轉為時間戳
time_t t = timegm(&tm);
return (long)t;
}
SDK示例代碼:
testReqMethod = "StrptimeEval";
tappExecuteRequest = TappExecuteRequest.builder()
.defaultRequest(tappId, tappVersion, testReqMethod)
.addString("2001-11-12 18:31:01")
.addString("%Y-%m-%d %H:%M:%S")
.addInt32(BigInteger.valueOf(8))
.build();
tappExecuteResponse = client.executeTappPrivately(tappExecuteRequest);
Assert.assertNotNull(tappExecuteResponse);
Assert.assertTrue(tappExecuteResponse.isRequestSuccess());
Assert.assertTrue(tappExecuteResponse.isExecuteSuccess());
Assert.assertEquals(1005561061, tappExecuteResponse.getReturnValue().toInt());
UNIX時間戳轉時間字符串:
strftime
說明要引用 <time.h> 頭文件。
#include <time.h>
INTERFACE String StrftimeEval(long ts, String fmt, int utc_offset) {
//長度128,此處是示例值。請設置為實際格式化后字符串的長度。
//此處指定長度可以大于實際長度
//如果小于實際長度,則會發生截斷
const size_t buffer_size = 128;
char buffer[buffer_size];
struct tm *pt;
// UNIX時間戳
time_t mytime = (time_t)(ts);
// struct tm *localtime(const time_t *t, int utc_hour_off)
// 時間戳轉本地時區tm結構
// 目前支持時區:CHINA_STANDARD_TIME_TZ_OFFSET (+8)
// GREENWICH_MEAN_TIME_TZ_OFFSET (0)
// 或者 直接提供本地時區與UTC之間差距,[-12, 12]
// 轉換失敗返回NULL。
pt = localtime(&mytime, utc_offset);
MyAssert(NULL != pt, "localtime");
// size_t strftime(char *s, size_t n, const char *f, const struct tm *tm);
// 轉換成格式化字符串
// fmt可以為 "%Y-%m-%d %H:%M:%S"
// 更多格式化參考:https://www.man7.org/linux/man-pages/man3/strftime.3.html
// 轉換失敗返回0
size_t len = strftime(buffer, buffer_size, fmt.c_str(), pt);
MyAssert(len != 0, "strftime");
return String{buffer, len};
}
SDK使用代碼:
// 發送端到端加密請求
testReqMethod = "StrftimeEval";
tappExecuteRequest = TappExecuteRequest.builder()
.defaultRequest(tappId, tappVersion, testReqMethod)
.addInt32(BigInteger.valueOf(1005561061))
.addString("%Y-%m-%d %H:%M:%S")
.addInt32(BigInteger.valueOf(8))
.build();
tappExecuteResponse = client.executeTappPrivately(tappExecuteRequest);
Assert.assertNotNull(tappExecuteResponse);
Assert.assertTrue(tappExecuteResponse.isRequestSuccess());
Assert.assertTrue(tappExecuteResponse.isExecuteSuccess());
Assert.assertEquals("2001-11-12 18:31:01", tappExecuteResponse.getReturnValue().toUtf8String());
三方庫
目前從MYCDT編譯器層面集成了若干三方庫。注意,三方庫是以編譯器庫函數的形式提供,因此函數會被編譯成WASM字節碼,增加了WASM的體積,運行速度也受到解釋執行的限制。而上述接口使用WASM外部集成的方式,在SGX內部原生實現,不參與WASM的編譯過程。
目前支持的三方庫如下:
返回值定義
ApiErrorCode
調用tapp庫接口的返回碼,我們會將所有接口返回碼統一到此。
enum ApiErrorCode: int {
kApiSuccess = 1,
kApiFail = 0,
kApiInternalError = -0x0001,
// ------------------------------------------
// COMMON ERROR CODE: [-0x2000, -0x2fff]
// ------------------------------------------
kApiDoNotUseSyncInterface = -0x2000,
// ------------------------------------------
// CRYPTO RELATED ERROR CODE: [-0x0002, -0x1fff]
// ------------------------------------------
// Crypto ErrorCode
// 輸入錯誤
kApiPrivateKeyBufferError = -0x0002,
kApiPublicKeyBufferError = -0x0003,
kApiDigestBufferError = -0x0004,
kApiDataBufferError = -0x0005,
kApiSignatureBufferError = -0x0006,
kApiInputDataError = -0x0007,
// 過程錯誤
kApiUnknownDigestName = -0x0008,
kApiPrivateKeyError = -0x0009,
kApiPublicKeyError = -0x000a,
// 輸出錯誤
kApiOutputBufferError = -0x000b,
// 授權信封錯誤
kApiEnvelopeFieldError = -0x000c,
kApiEnvelopeProofFieldError = -0x000d,
kApiUnsupportType = -0x000e,
kApiInvalidDid = -0x000f,
kApiInvalidVC = -0x0010,
kApiInvalidAuth = -0x0011,
kApiSubjectNotMatch = -0x0012,
kApiAuthNotMatch = -0x0013,
kApiInvalidSignature = -0x0014,
kApiInvalidDidProof = -0x0015,
kApiInvalidCallerSig = -0x0016,
// 其他
kApiGasExhausted = -0x0017,
// 傳入參數 md type 錯誤
kApiInvalidMdType = -0x0018,
kApiDecryptFail = -0x0019,
// reserve for more CryptoErrorCode
// [-0x0019, -0x00FF]
// mychain crypto lib errorcode
// [-0x0100, -0x1FFF]
// reserve for more MychainCrypto
// ------------------------------------------
// FILE RELATED ERROR CODE: [-0x4000, -0x4fff]
// ------------------------------------------
// 權限
kApiFilePermissionInvalid = -0x4000,
// 文件句柄
kApiFileNotOpened = -0x4100, // tfile 句柄未打開
kApiFileOpenedExceedTotalLimit = -0x4101,
kApiFileOpenedExceedRequestLimit = -0x4102,
// data address
kApiFileNonSupportedFileSourceType = -0x4200, // file meta 地址格式錯誤,合法應該為 xx.meta.hash
kApiFileMetaAddressFormatError = -0x4201, // file meta 地址格式錯誤,合法應該為 xx.meta.hash
// data limit
kApiFileLineExceedLimit = -0x4300,
kApiFileSliceExceedLimit = -0x4301,
kApiFileSliceCountExceedLimit = -0x4302,
kApiFileMetaExceedLimit = -0x4303,
kApiFileChunkExceedLimit = -0x4304,
kApiFileMalformedChunk = -0x4305,
kApiFileMalformedFileMeta = -0x4306,
kApiFileMalformedSlice = -0x4307,
kApiFileIsEmpty = -0x4308,
kApiJoinFileExceedLimit = -0x4309, // 求交算法join文件太大
kApiResultLineExceedLimit = -0x430a, // 求交算法結果行超出限制
// data IO
kApiFileOssObjectUploadFailed = -0x4400,
kApiFileOssObjectDownloadFailed = -0x4401,
kApiFileOssObjectNotFound = -0x4402,
kApiFileOssObjectTooLarge = -0x4403,
// data read & write
kApiFileNonSupportedDecoder = -0x4500,
kApiFileNonSupportedEncoder = -0x4501,
kApiFileEndOfTfile = -0x4502, // tfile文件已讀到結束
// auxiliary
kApiFileMalformedAuxiliary = -0x4600,
kApiFileAuxiliaryCheckFailed = -0x4601,
kApiFileNonSupportFileType = -0x4602,
// runtime
kApiFileStatusInvalid = -0x4700,
// integrity
kApiFileSliceRootHashError = -0x4800, // slice root hash 完整新校驗失敗
kApiFileMetaHashError = -0x4801, // slice root hash 完整新校驗失敗
};
CryptoErrorCode
我們會將所有接口返回碼統一到 ApiErrorCode
,此返回碼會保留。
// MYTF 密碼操作相關接口返回值
enum CryptoErrorCode: int {
kSuccess = 1,
kFail = 0,
kInternalError = -0x0001,
// 輸入錯誤
kPrivateKeyBufferError = -0x0002,
kPublicKeyBufferError = -0x0003,
kDigestBufferError = -0x0004,
kDataBufferError = -0x0005,
kSignatureBufferError = -0x0006,
kInputDataError = -0x0007,
// 過程錯誤
kUnknownDigestName = -0x0008,
kPrivateKeyError = -0x0009,
kPublicKeyError = -0x000a,
// 輸出錯誤
kOutputBufferError = -0x000b,
// 授權信封錯誤
kEnvelopeFieldError = -0x000c,
kEnvelopeProofFieldError = -0x000d,
kUnsupportType = -0x000e,
kInvalidDid = -0x000f,
kInvalidVC = -0x0010,
kInvalidAuth = -0x0011,
kSubjectNotMatch = -0x0012,
kAuthNotMatch = -0x0013,
kInvalidSignature = -0x0014,
kInvalidDidProof = -0x0015,
kInvalidCallerSig = -0x0016,
// 其他
kGasExhausted = -0x0017,
};
常見問題
C++ 標準支持情況
從安全與審計角度考慮,不推薦使用以下基礎設施:
指針/引用。指針的越界行為是 C99/C++14 中最難以捉摸的行為,因此審計時需要格外小心。
數組。數組的越界是 C++ 中的常見錯誤且很難排查。TAPP語言從正交化的角度考慮提供 std::vector 來替代。
enum/enum class/union。enum(作為弱類型枚舉)、enum class(作為強類型枚舉)及 union 的內存布局與標準 C++ 一致,但和數組一樣,類型檢查與越界檢測很難發揮效力。
包括重載、模板與繼承,TAPP語言對這些基礎設施支持良好,且允許組合使用。
標準庫支持
malloc/free、new/delete 等內存管理類操作。已改寫以保證安全性。
abort/exit 等進程控制類操作。已改寫以保證安全性,不應在TAPP 中使用。
iostream/cstdio 中所包含的 IO 操作。TAPP語言不允許進行類似操作,同時提供了與 C++ 中 printf 行為相仿的 print 接口供TAPP開發者本地調試與輸出使用。
對 std::vector 與 std::string 以外的 C++14 標準庫所支持類型與 可序列化數據類型 中所描述的可序列化類型的序列化行為未作嚴格定義,因此無法作為函數參數傳遞。
系統調用支持
不支持調用分類及細分說明:
網絡:DNS、TCP、UDP、IPv6、以太網、socket、epoll、poll、select 等。
IO:文件、設備 ioctl、多緩沖區讀寫、內存映射。
系統內核信息:進程分析 ptrace、磁盤使用信息、系統文件使用情況統計、Linux 文件系統操作。
系統調度:動態加載、cache 控制、重啟。
進程間通信:消息機制、信號量、共享內存、信號處理、IPC。
線程調度:線程、鎖、用戶層進程內多線程上下文切換、定時器 sleep、wait。
系統權限控制:賬戶體系、文件權限。
其他:歸檔、復數、正則表達式、終端仿真 mount、系統調用入口 syscall、時間、隨機數、編碼轉換。 iconv、系統日志、“long double”相關運算與函數。
編譯選項支持
編譯工具使用以下編譯參數對某些 C++ 特性做了限制。注意,編寫 TAPP 時不要使用被禁止的特性:
-fno-cfl-aa 禁用 CFL 別名分析。
-fno-elide-constructors 禁用復制構造函數的 復制消除行為。
-fno-lto 禁用鏈接時優化。
-fno-rtti 禁用 RTTI。
-fno-exceptions 禁用異常。
-fno-threadsafe-statics 禁用靜態局部變量的線程安全特性。
編譯器安裝或使用失敗
目前在 Ubuntu、CentOS、AliOS 和 Mac OS 均進行了測試,其中 Ubuntu 某些環境會出現編譯問題。解決途徑:? 編譯器 my++
和mychain的編譯器 my++
重名,請檢查路徑中是否有其他 my++
(which my++
)。運行 my++ --verison
應該返回 “my++ version mytf-VERSION(commitid)”。? 將需要編譯的文件 xx.cpp
移入 $MYCDT_INSTALL_HOME/bin/
文件夾,在此文件夾直接運行my++ xx.cpp -o xx.wasm
。
結果返回“abort called”?
一般是tapp程序調用 abort()
導致,如JSON庫在面對非法字段訪問的時候,會調用abort
。產生此錯誤請仔細檢查tapp程序
結果返回“res[8450]: out of bounds memory access”?
Tapp目前支持最大16MB內存,超出此內存限制則會返回此錯誤。出現此錯誤,請仔細檢查Tapp的內存使用量,及時釋放不需要的內存。優化示例如下:
// 此處s占用10M空間,s2再次申請10M空間,則會內存超限
std::string s(10 * 1024 * 1024, 'a');
DoSomething(s);
std::string s2(10 * 1024 * 1024, 'a');
// 優化方式1: 我們使用{}將s的作用域限制一下,那么在}處,s就會被釋放。s2就能正常申請內存
{
std::string s(10 * 1024 * 1024, 'a');
DoSomething(s);
}
std::string s2(10 * 1024 * 1024, 'a');
// 優化方式2:我們在s使用結束后及時清空s的內存。s2就能正常申請內存
// 使用下面第3行所示的swap()方式清空s內存。注意,調用s.clear()并不會立即清空內存,推薦使用swap方式
std::string s(10 * 1024 * 1024, 'a');
DoSomething(s);
std::string().swap(s);
std::string s2(10 * 1024 * 1024, 'a');