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');