可信计算应用开发

TAPP 可信应用是运行在可信执行环境中,持有密钥的 WebAssembly 智能合约。TAPP 提供了一套基于 C99/C++14 标准的 C++ 语言子集作为合约语言。TAPP 开发者在开发可信计算应用程序时,需要先下载、安装 TAPP 编译工具 mytf.mycdt, 通过编译工具将编写的 TAPP 代码编译成 WASM 字节码。之后,将TAPP 字节码安装到 MYTF 可信计算引擎中,由 MYTF 对 WASM 字节码进行解释并执行。

编写 TAPP

TAPP 代码基本框架如下(sample.cpp):

  1. // 基本库,包含了全部MYTF库函数
  2. #include <mychainlib/contract.h>
  3. // 三方库,本示例引入JSON库
  4. #include <third_party/rapidjson/document.h>
  5. #include <third_party/rapidjson/stringbuffer.h>
  6. #include <third_party/rapidjson/writer.h>
  7. // 基本MYTF库函数的命名空间
  8. using namespace mychain;
  9. // TAPP主类,与C++类定义相同,区别在于继承Contract
  10. class MyTapp : public Contract {
  11. public:
  12. // TAPP函数,与C++函数定义相同。区别在于,如果是外部可调用接口,使用INTERFACE宏
  13. INTERFACE std::string Encode(const std::string& data) {
  14. std::string out;
  15. // 调用MYTF库函数提供的接口(API):Base64Encode
  16. CryptoErrorCode err = Base64Encode(data, out);
  17. if (err != CryptoErrorCode::kSuccess) {
  18. // 目前打印至MYTF日志
  19. print("failed to base64encode: %d", err);
  20. return std::to_string(err);
  21. }
  22. return out;
  23. }
  24. // TAPP函数,与C++函数定义相同。区别在于,如果是外部可调用接口,使用INTERFACE宏
  25. INTERFACE int Add(int a, int b) {
  26. return a + b;
  27. }
  28. };
  29. // 通过宏定义外部可调用函数
  30. INTERFACE_EXPORT(MyTapp, (Encode) (Add))

下载编译工具

目前,编译工具支持 Linux、MacOS 系统,版本信息及对应下载地址如下:

名称 版本 下载地址
mytf.mycdt 0.2.2.1 点击下载 MacOS 版本

点击下载 Linux 版本

该编译工具链基于 Clang。开发者可以通过 mytf.mycdt 将可信计算应用代码编译成 WASM 字节码,然后用 MYTF SDK 将 TAPP 安装到 C3S MYTF 中。MYTF 可信计算引擎会对 WASM 字节码进行解释执行。

安装编译工具

下载编译工具后,将工具包解压到指定目录,如 $HOME/mycdt,并设置 PATH 环境变量。

注意my++ --version 取决于安装的版本。

  1. $ export PATH=$PATH:$HOME/mycdt/bin
  2. $ my++ --version
  3. my++ version mytf-0.2.2.1

编译应用

下面以可信应用 sample.cpp 为例编译应用。

  1. $ my++ sample.cpp -o sample.wasm

编译完成后,生成 WASM 字节码 sample.wasm。可以通过 MYTF SDK 将该字节码 安装至 MYTF 可信计算引擎中进行解释执行,具体操作参见 SDK 使用指南

API 详解

除 C++ 标准库之外,MYTF 提供了若干库函数和 API 接口,可以在 TAPP 中使用,提高易用性和性能。API 分类介绍如下:

基础设施接口

MyAssert & Require

  1. // 函数原型
  2. // 如果(not expression),则终止程序,将msg信息写入Output中
  3. // (每次tapp调用会得到return_val和Output,其中output是系统级别信息,详见SDK文档)
  4. // (以下不再赘述Output)
  5. void MyAssert(int expression, const std::string& msg);
  6. // 如果(not condition),则终止程序,将exception信息写入Output中
  7. // 返回值为0,无意义
  8. int Require(bool condition, const std::string& exception);
  9. // 两个接口自Mychain智能合约就存在,差异不大
  10. // 使用示例
  11. INTERFACE std::string Encode(const std::string& data) {
  12. std::string out;
  13. // 调用MYTF库函数提供的接口(API):Base64Encode
  14. CryptoErrorCode err = Base64Encode(data, out);
  15. MyAssert(err == CryptoErrorCode::kSuccess, "failed to base64encode");
  16. return out;
  17. }

LOG_*

包括 LOG_DEBUGLOG_INFOLOG_ERROR。打印调试信息。每次输出信息作为一行,如 LOG_DEBUG("%d", INT32_MAX);将会输出 “[DEBUG] 2147483647\n”。每行最大长度为512,超出此长度则会进行截断。

  1. // 原型
  2. // 格式与 C99 中 printf 相仿
  3. int LOG_DEBUG(const char* format, ...);
  4. // 示例
  5. INTERFACE void TestPrint() {
  6. LOG_DEBUG("%d", INT32_MAX); // 2147483647
  7. LOG_DEBUG("%d", -1); // -1
  8. LOG_DEBUG("%u", -1); // 4294967295
  9. LOG_DEBUG("%o", 16); // 20
  10. LOG_DEBUG("%x", 16); // 10
  11. LOG_DEBUG("%f", -1.01); // -1.010000
  12. LOG_DEBUG("%c", 'a'); // a
  13. LOG_INFO("%p", 0); // 00000000
  14. LOG_INFO("%s", "hello"); // hello
  15. LOG_INFO("%%"); // %
  16. LOG_ERROR("hello"); // hello
  17. LOG_ERROR(""); //
  18. }

一次 tapp 调用期间,LOG 信息会保存在一个 rotate log buffer 内,调用结束之后将返回给调用者(ExecuteRes.log)。log buffer 大小目前设定为200k, 超出限制也会滚动写入。如通过 LOG_DEBUG 写入30k 行,每行20 个字节,则只有最后 10k 行会保留下来,返回给调用者。tapp 安装时可以指定日志级别:

  1. enum TappLogLevel {
  2. kTappLogDebug = 0,
  3. kTappLogInfo = 1,
  4. kTappLogError = 2,
  5. kTappLogDisable = 3,
  6. };

默认为kTappLogDisable,即不输出任何日志。低于设定日志级别的 LOG 操作将不会生效。如安装 tapp 时指定日志级别为 kTappLogError,则 tapp 中的 LOG_DEBUGLOG_INFO 将不会打印信息。

Bin2Hex & Hex2Bin

  1. // 函数原型
  2. // 每个字节转化为两个字节(hex模式)。如"\0\255" => "00ff" 或 "00FF" (uppercase = true)
  3. std::string Bin2Hex(const std::string& input, bool uppercase = false);
  4. // 上述函数的逆操作,允许0x或0X开头
  5. std::string Hex2Bin(const std::string& input);

信封接口

EnvelopeOpen & EnvelopeBuild

TAPP 内部可以使用该函数进行基于 ECIES-SECP256K1 /国密算法的加密解密。支持多方用户数据加密后,由第三方服务平台融合多方加密数据并请求执行 TAPP,服务平台无法获取用户隐私数据。

envelope

  1. // 函数原型
  2. // 解密信封envelope,得到其中的plain_data。信封构造参考SDK文档
  3. CryptoErrorCode EnvelopeOpen(const std::string& envelope, std::string& plain_data);
  4. // 构造信封,使用pk使用algo算法对plain_data进行加密,得到加密后的信封
  5. 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);
  6. // 实例
  7. INTERFACE std::string TestEnvelopeBuild(std::string plain_data, std::string pk) {
  8. print("plain_data %s are ready to be encrypt", plain_data.c_str());
  9. std::string ret_envelope = "";
  10. CryptoErrorCode ret = EnvelopeBuild(pk, plain_data, ret_envelope);
  11. if(CryptoErrorCode::kSuccess != ret) {
  12. print("Failed to encrypt envelope");
  13. return std::to_string(ret);
  14. }
  15. return ret_envelope;
  16. }
  17. INTERFACE std::string TestEnvelopeOpen(std::string envelope_data) {
  18. std::string plain_data = "";
  19. CryptoErrorCode ret = EnvelopeOpen(envelope_data, plain_data);
  20. if(CryptoErrorCode::kSuccess != ret) {
  21. print("Failed to decrypt envelope");
  22. return std::to_string(ret);
  23. }
  24. print("Decrypted data: %s", plain_data.c_str());
  25. return plain_data;
  26. }

ECElgamalEnvelopeDecrypt

TAPP 内部可以使用该函数进行基于 ECElgamal-SECP256K1 算法的解密。支持数据一方加密后多方解密。

ecenvelope

  1. // 原型
  2. // 指定curve_type与prikey,对cipher解密得到output
  3. CryptoErrorCode ECElgamalEnvelopeOpen(uint32_t curve_type, const std::string& prikey, const std::string& cipher, std::string& output);
  4. // 示例
  5. INTERFACE std::string TestECElgamalEnvelopeDecrypt(uint32_t pk_num, std::string prikey, std::string cipher_data) {
  6. uint32_t curve_type = 0;
  7. std::string plain_data = "";
  8. CryptoErrorCode ret = ECElgamalEnvelopeOpen(curve_type, prikey, cipher_data, plain_data);
  9. if (CryptoErrorCode::kSuccess != ret) {
  10. print("Failed to decrypt envelope");
  11. return std::to_string(ret);
  12. }
  13. return plain_data;
  14. }

AuthEnvelopeDecrypt

在 MYTF 中,每个计算 TAPP 拥有加密公私钥。数据所有者需要使用 TAPP 的公钥加密,对隐私构造 AuthEnvelope 数据权属信封结构,对数据的使用权限进行把控,保证了只有该 TAPP 才能解密此数据。当数据所有者需要将隐私数据使用权授信给其他人时,需要签发 VC 给数据使用者,明确谁可以使用此数据执行 TAPP 的哪些函数。当 TAPP 收到数据使用者的计算请求时,会校验使用者是否具有隐私数据的使用权限。

authenvelope

  1. // 原型
  2. // 数据拥有者的加密数据envelope,调用者提供拥有者的授权authorization和自身身份证明id_proof。调用此接口得到原文数据plain
  3. CryptoErrorCode AuthEnvelopeOpen(const std::string& envelope, const std::string& authorization, const std::string& id_proof, std::string& plain /* out */);
  4. // 示例
  5. INTERFACE std::string TestAuthEnvelopeDecrypt(std::string envelope_data, std::string auth, std::string id_proof) {
  6. std::string plain_data = "";
  7. CryptoErrorCode res_code = AuthEnvelopeOpen(envelope_data, auth, id_proof, plain_data);
  8. if (CryptoErrorCode::kSuccess != res_code) {
  9. print("Failed to decrypt authenvelope: %u", res_code);
  10. return std::to_string(res_code);
  11. }
  12. print("Decrypted auth data: %s", plain_data.c_str());
  13. return plain_data;
  14. }

密码接口

RsaSign & RsaVerify

  1. // 原型
  2. // 使用pri_key,采用digest_name摘要方式,对data签名得到sign
  3. CryptoErrorCode RsaSign(const std::string& pri_key, const std::string& digest_name, const std::string& data, std::string& sign);
  4. // 使用pub_key,采用digest_name摘要方式,验证data的签名sign
  5. CryptoErrorCode RsaVerify(const std::string& pub_key, const std::string& digest_name, const std::string& data, const std::string& sign);
  6. // 示例
  7. INTERFACE std::vector<std::string> OrderVerify() {
  8. std::vector<std::string> ret;
  9. // 密码配置
  10. const std::string digest_name = "SHA1";
  11. const std::string pri_key = "-----BEGIN RSA PRIVATE KEY-----\nXXBase64XX\n-----END RSA PRIVATE KEY-----";
  12. const std::string pub_key = "XXBase64XX";
  13. // 获取签名
  14. std::string encode_params = "some data";
  15. std::string sign;
  16. CryptoErrorCode sign_err = RsaSign(pri_key, digest_name, encode_params, sign);
  17. if (sign_err != CryptoErrorCode::kSuccess) {
  18. print("failed to sign: %u", sign_err);
  19. ret.push_back("FAIL"); ret.push_back("failed to sign"); ret.push_back(std::to_string(sign_err));
  20. return ret;
  21. }
  22. std::string encode_resp = "some data";
  23. std::string resp_sign = "XXBase64XX";
  24. // 验证签名
  25. CryptoErrorCode verify_err = RsaVerify(pub_key, digest_name, encode_resp, resp_sign);
  26. if (verify_err != CryptoErrorCode::kSuccess) {
  27. print("failed to verify sig: %u", verify_err);
  28. ret.push_back("FAIL"); ret.push_back("failed to verify signature"); ret.push_back(std::to_string(verify_err));
  29. }
  30. return ret;
  31. }

TappEcdsaSign

TAPP函数:使用TAPP的私钥对 data 进行签名,签名算法为 SECP256K1 ,内部实现使用bitcoin优化过的 secp256k1 算法。验签端注意对此算法对齐。

  • 函数原型
  1. /*
  2. 功能:使用当前 TAPP 的签名私钥,对 data 进行签名。摘要算法为 SHA256,签名算法曲线为 curve_name,默认为 SECP256K1
  3. 返回值:执行结果,int 型枚举类型。具体错误码解释见后文
  4. 参数:
  5. data: 入参,需要签名的数据
  6. sign: 出参,得到签名的结果。
  7. curve_name: 缺省入参,签名算法使用的曲线,可选 ECDSA_RAW_SECP256K1_KEY或ECDSA_SM2P256V1_KEY
  8. */
  9. CryptoErrorCode TappEcdsaSign(const std::string& data, std::string& sign, KEY_ALGO_TYPE curve_name = KEY_ALGO_TYPE::ECDSA_RAW_SECP256K1_KEY);

Sha256

  1. // 原型
  2. // 计算msg的SHA256摘要,得到hash
  3. CryptoErrorCode Sha256(const std::string& msg, std::string& hash);

Hmac

  1. /* 原型
  2. * 计算Hmac, 使用md_type摘要算法,使用key对msg计算hmac,得到结果mac
  3. * 其中摘要算法类型
  4. enum MdType: int {
  5. kSha1 = 0,
  6. kSha256 = 1,
  7. };
  8. */
  9. CryptoErrorCode Hmac(const MdType& md_type, const std::string& msg, const std::string& key, std::string& mac);
  10. // 示例
  11. String hmac_key = "abcd";
  12. String hmac_data = "hello";
  13. String hmac_out;
  14. CryptoErrorCode ret = Hmac(MdType::kSha1, hmac_data, hmac_key, hmac_out);
  15. MyAssert(CryptoErrorCode::kSuccess == ret, "hmac errcode");
  16. MyAssert(Bin2Hex(hmac_out) == "5126823fdb3f4ee3f970f8274929a50bbd5c8d0c", "hmac result");

国密接口

支持 Sm3_256Sm2Sm3SignSm2Sm3VerifySm2Sm3EncryptSm2Sm3DecryptSm4GcmEncryptSm4GcmDecrypt,示例如下:

  1. // 函数原型
  2. // const参数msg为入参,非const参数hash为结果出参。使用SM3 256算法,对msg计算摘要hash
  3. // 如无特殊说明,以下const参数均为入参,非const参数均为结果出参。函数和参数名清晰,不再赘述具体含义
  4. CryptoErrorCode Sm3_256(const std::string& msg, std::string& hash);
  5. CryptoErrorCode Sm2Sm3Sign(const std::string& msg, const std::string& prikey, std::string& signature);
  6. CryptoErrorCode Sm2Sm3Verify(const std::string& msg, const std::string& pubkey, const std::string& signature);
  7. CryptoErrorCode Sm2Sm3Encrypt(const std::string& plain, const std::string& pubkey, std::string& cipher);
  8. CryptoErrorCode Sm2Sm3Decrypt(const std::string& cipher, const std::string& prikey, std::string& plain);
  9. CryptoErrorCode Sm4GcmEncrypt(const std::string& plain, const std::string& secret_key, std::string& cipher);
  10. CryptoErrorCode Sm4GcmDecrypt(const std::string& cipher, const std::string& secret_key, std::string& plain);
  11. // 示例
  12. // 综合测试SM所有接口
  13. INTERFACE int TestSM(String plain, String expected_hash, String pubkey, String prikey, String secret_key) {
  14. // Sm3 hash
  15. String hash;
  16. CryptoErrorCode ret = Sm3_256(plain, hash);
  17. MyAssert(CryptoErrorCode::kSuccess == ret, "sm3 256");
  18. MyAssert(hash == expected_hash, "test sm3_256");
  19. // Sm2Sm3 sign
  20. String signature;
  21. ret = Sm2Sm3Sign(plain, prikey, signature);
  22. MyAssert(CryptoErrorCode::kSuccess == ret, "sm2 sm3 sign");
  23. ret = Sm2Sm3Verify(plain, pubkey, signature);
  24. MyAssert(CryptoErrorCode::kSuccess == ret, "sm2 sm3 verify");
  25. // Sm2Sm3 encrypt
  26. String cipher;
  27. ret = Sm2Sm3Encrypt(plain, pubkey, cipher);
  28. MyAssert(CryptoErrorCode::kSuccess == ret, "sm2 sm3 encrypt");
  29. String plain_decrypted;
  30. ret = Sm2Sm3Decrypt(cipher, prikey, plain_decrypted);
  31. MyAssert(CryptoErrorCode::kSuccess == ret, "sm2 sm3 decrypt");
  32. MyAssert(plain == plain_decrypted, "test sm2 sm3 encrypt");
  33. // Sm4 encrypt
  34. ret = Sm4GcmEncrypt(plain, secret_key, cipher);
  35. MyAssert(CryptoErrorCode::kSuccess == ret, "sm4 gcm encrypt");
  36. ret = Sm4GcmDecrypt(cipher, secret_key, plain_decrypted);
  37. MyAssert(CryptoErrorCode::kSuccess == ret, "sm4 gcm decrypt");
  38. MyAssert(plain == plain_decrypted, "test sm4 gcm encrypt");
  39. return 0;
  40. }

对称加解密接口

支持 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),参考代码见下:

  1. /**
  2. 函数原型:AesGcmDecrypt
  3. 参数说明:
  4. key: 加密密钥,合法长度16/24/32字节
  5. iv: Initialization_Vector, 12字节
  6. aad: Additional Authenticated Data
  7. cipher: 密文
  8. plain[out]: 解密后明文
  9. 返回值:
  10. CryptoErrorCode 错误码
  11. */
  12. CryptoErrorCode AesGcmDecrypt(const std::string& key, const std::string& iv, const std::string& aad, const std::string& cipher, std::string& plain);
  13. // 代码示例
  14. INTERFACE String TestAesGcmDecrypt(String key, String iv, String aad, String cipher) {
  15. String out;
  16. CryptoErrorCode err = AesGcmDecrypt(key, iv, aad, cipher, out);
  17. if (err != CryptoErrorCode::kSuccess) {
  18. print("failed to AesGcmDecrypt: %d", err);
  19. return std::to_string(err);
  20. }
  21. print("TestAesGcmDecrypt, out size %d, %s", out.size(), out.c_str());
  22. return out;
  23. }
  24. /**
  25. 函数原型:AesGcmEncrypt
  26. 参数说明:
  27. key: 加密密钥,合法长度16/24/32字节
  28. iv: Initialization_Vector, 12字节
  29. aad: Additional Authenticated Data
  30. plain: 明文
  31. cipher[out]: 加密后密文
  32. 返回值:
  33. CryptoErrorCode 错误码
  34. */
  35. CryptoErrorCode AesGcmEncrypt(const std::string& key, const std::string& iv, const std::string& aad, const std::string& plain, std::string& cipher);
  36. // 示例代码
  37. INTERFACE String TestAesGcmEncrypt(String key, String iv, String aad, String plain) {
  38. String out;
  39. CryptoErrorCode err = AesGcmEncrypt(key, iv, aad, plain, out);
  40. if (err != CryptoErrorCode::kSuccess) {
  41. print("failed to AesGcmEncrypt: %d", err);
  42. return std::to_string(err);
  43. }
  44. print("TestAesGcmEncrypt, out size %d, %s", out.size(), out.c_str());
  45. return out;
  46. }
  47. /**
  48. 函数原型:AesEcbDecrypt
  49. 参数说明:
  50. key: 加密密钥,合法长度16/24/32字节
  51. cipher: 密文
  52. plain[out]: 解密后明文
  53. 返回值:
  54. CryptoErrorCode 错误码
  55. */
  56. CryptoErrorCode AesEcbDecrypt(const std::string& key, const std::string& cipher, std::string& plain);
  57. // 示例代码
  58. INTERFACE String TestAesEcbDecrypt(String key, String cipher) {
  59. String out;
  60. CryptoErrorCode err = AesEcbDecrypt(key, cipher, out);
  61. if (err != CryptoErrorCode::kSuccess) {
  62. print("failed to AesEcbDecrypt: %d", err);
  63. return std::to_string(err);
  64. }
  65. print("TestAesEcbDecrypt, out %s", out.c_str());
  66. return out;
  67. }
  68. /**
  69. 函数原型:AesEcbEncrypt
  70. 参数说明:
  71. key: 加密密钥,合法长度16/24/32字节
  72. plain: 明文
  73. cipher: 加密后密文
  74. 返回值:
  75. CryptoErrorCode 错误码
  76. */
  77. CryptoErrorCode AesEcbEncrypt(const std::string& key, const std::string& plain, std::string& cipher);
  78. // 示例代码
  79. INTERFACE String TestAesEcbEncrypt(String key, String plain) {
  80. String out;
  81. CryptoErrorCode err = AesEcbEncrypt(key, plain, out);
  82. if (err != CryptoErrorCode::kSuccess) {
  83. print("failed to AesEcbEncrypt: %d", err);
  84. return std::to_string(err);
  85. }
  86. print("TestAesEcbEncrypt, out %s", out.c_str());
  87. return out;
  88. }

Tools

Base64EncodeBase64Decode 的示例如下:

  1. // 原型
  2. CryptoErrorCode Base64Encode(const std::string& input, std::string& output);
  3. CryptoErrorCode Base64Decode(const std::string& input, std::string& output);

时间处理接口

SDK版本要求: 0.2.2.2+。

  • 时间字符串转UNIX时间戳: strptime

    要引用 <time.h> 头文件

  1. INTERFACE long StrptimeEval(String time_string, String fmt, int utc_offset) {
  2. struct tm tm;
  3. memset(&tm, 0, sizeof(struct tm));
  4. // char *strptime_tz(const char *restrict s, const char *restrict f, int utc_hour_off, struct tm *restrict tm)
  5. // 按给定时区(UTC偏移)将本地时间字符串转换为本地时间tm结构
  6. // 目前支持时区:CHINA_STANDARD_TIME_TZ_OFFSET
  7. // GREENWICH_MEAN_TIME_TZ_OFFSET
  8. // 或者 直接提供本地时区与UTC之间差距
  9. char* s = strptime_tz(time_string.c_str(), fmt.c_str(), utc_offset, &tm);
  10. MyAssert(s != NULL, "strptime");
  11. // 转为时间戳
  12. time_t t = timegm(&tm);
  13. return (long)t;
  14. }

sdk示例代码:

  1. testReqMethod = "StrptimeEval";
  2. tappExecuteRequest = TappExecuteRequest.builder()
  3. .defaultRequest(tappId, tappVersion, testReqMethod)
  4. .addString("2001-11-12 18:31:01")
  5. .addString("%Y-%m-%d %H:%M:%S")
  6. .addInt32(BigInteger.valueOf(8))
  7. .build();
  8. tappExecuteResponse = client.executeTappPrivately(tappExecuteRequest);
  9. Assert.assertNotNull(tappExecuteResponse);
  10. Assert.assertTrue(tappExecuteResponse.isRequestSuccess());
  11. Assert.assertTrue(tappExecuteResponse.isExecuteSuccess());
  12. Assert.assertEquals(1005561061, tappExecuteResponse.getReturnValue().toInt());
  • UNIX时间戳转时间字符串: strftime

    要引用 <time.h> 头文件。

  1. #include <time.h>
  2. INTERFACE String StrftimeEval(long ts, String fmt, int utc_offset) {
  3. //长度128,此处是示例值。请设置为实际格式化后字符串的长度。
  4. //此处指定长度可以大于实际长度
  5. //如果小于实际长度,则会发生截断
  6. const size_t buffer_size = 128;
  7. char buffer[buffer_size];
  8. struct tm *pt;
  9. // UNIX时间戳
  10. time_t mytime = (time_t)(ts);
  11. // struct tm *localtime(const time_t *t, int utc_hour_off)
  12. // 时间戳转本地时区tm结构
  13. // 目前支持时区:CHINA_STANDARD_TIME_TZ_OFFSET (+8)
  14. // GREENWICH_MEAN_TIME_TZ_OFFSET (0)
  15. // 或者 直接提供本地时区与UTC之间差距,[-12, 12]
  16. // 转换失败返回NULL。
  17. pt = localtime(&mytime, utc_offset);
  18. MyAssert(NULL != pt, "localtime");
  19. // size_t strftime(char *s, size_t n, const char *f, const struct tm *tm);
  20. // 转换成格式化字符串
  21. // fmt可以为 "%Y-%m-%d %H:%M:%S"
  22. // 更多格式化参考:https://www.man7.org/linux/man-pages/man3/strftime.3.html
  23. // 转换失败返回0
  24. size_t len = strftime(buffer, buffer_size, fmt.c_str(), pt);
  25. MyAssert(len != 0, "strftime");
  26. return String{buffer, len};
  27. }

sdk使用代码:

  1. // 发送端到端加密请求
  2. testReqMethod = "StrftimeEval";
  3. tappExecuteRequest = TappExecuteRequest.builder()
  4. .defaultRequest(tappId, tappVersion, testReqMethod)
  5. .addInt32(BigInteger.valueOf(1005561061))
  6. .addString("%Y-%m-%d %H:%M:%S")
  7. .addInt32(BigInteger.valueOf(8))
  8. .build();
  9. tappExecuteResponse = client.executeTappPrivately(tappExecuteRequest);
  10. Assert.assertNotNull(tappExecuteResponse);
  11. Assert.assertTrue(tappExecuteResponse.isRequestSuccess());
  12. Assert.assertTrue(tappExecuteResponse.isExecuteSuccess());
  13. Assert.assertEquals("2001-11-12 18:31:01", tappExecuteResponse.getReturnValue().toUtf8String());

三方库

目前从MYCDT编译器层面集成了若干三方库。注意,三方库是以编译器库函数的形式提供,因此函数会被编译成WASM字节码,增加了WASM的体积,运行速度也受到解释执行的限制。而上述接口使用WASM外部集成的方式,在SGX内部原生实现,不参与WASM的编译过程。

目前支持的三方库如下:

返回值定义

ApiErrorCode

调用tapp库接口的返回码,我们会将所有接口返回码统一到此。

  1. enum ApiErrorCode: int {
  2. kApiSuccess = 1,
  3. kApiFail = 0,
  4. kApiInternalError = -0x0001,
  5. // ------------------------------------------
  6. // COMMON ERROR CODE: [-0x2000, -0x2fff]
  7. // ------------------------------------------
  8. kApiDoNotUseSyncInterface = -0x2000,
  9. // ------------------------------------------
  10. // CRYPTO RELATED ERROR CODE: [-0x0002, -0x1fff]
  11. // ------------------------------------------
  12. // Crypto ErrorCode
  13. // 输入错误
  14. kApiPrivateKeyBufferError = -0x0002,
  15. kApiPublicKeyBufferError = -0x0003,
  16. kApiDigestBufferError = -0x0004,
  17. kApiDataBufferError = -0x0005,
  18. kApiSignatureBufferError = -0x0006,
  19. kApiInputDataError = -0x0007,
  20. // 过程错误
  21. kApiUnknownDigestName = -0x0008,
  22. kApiPrivateKeyError = -0x0009,
  23. kApiPublicKeyError = -0x000a,
  24. // 输出错误
  25. kApiOutputBufferError = -0x000b,
  26. // 授权信封错误
  27. kApiEnvelopeFieldError = -0x000c,
  28. kApiEnvelopeProofFieldError = -0x000d,
  29. kApiUnsupportType = -0x000e,
  30. kApiInvalidDid = -0x000f,
  31. kApiInvalidVC = -0x0010,
  32. kApiInvalidAuth = -0x0011,
  33. kApiSubjectNotMatch = -0x0012,
  34. kApiAuthNotMatch = -0x0013,
  35. kApiInvalidSignature = -0x0014,
  36. kApiInvalidDidProof = -0x0015,
  37. kApiInvalidCallerSig = -0x0016,
  38. // 其他
  39. kApiGasExhausted = -0x0017,
  40. // 传入参数 md type 错误
  41. kApiInvalidMdType = -0x0018,
  42. kApiDecryptFail = -0x0019,
  43. // reserve for more CryptoErrorCode
  44. // [-0x0019, -0x00FF]
  45. // mychain crypto lib errorcode
  46. // [-0x0100, -0x1FFF]
  47. // reserve for more MychainCrypto
  48. // ------------------------------------------
  49. // FILE RELATED ERROR CODE: [-0x4000, -0x4fff]
  50. // ------------------------------------------
  51. // 权限
  52. kApiFilePermissionInvalid = -0x4000,
  53. // 文件句柄
  54. kApiFileNotOpened = -0x4100, // tfile 句柄未打开
  55. kApiFileOpenedExceedTotalLimit = -0x4101,
  56. kApiFileOpenedExceedRequestLimit = -0x4102,
  57. // data address
  58. kApiFileNonSupportedFileSourceType = -0x4200, // file meta 地址格式错误,合法应该为 xx.meta.hash
  59. kApiFileMetaAddressFormatError = -0x4201, // file meta 地址格式错误,合法应该为 xx.meta.hash
  60. // data limit
  61. kApiFileLineExceedLimit = -0x4300,
  62. kApiFileSliceExceedLimit = -0x4301,
  63. kApiFileSliceCountExceedLimit = -0x4302,
  64. kApiFileMetaExceedLimit = -0x4303,
  65. kApiFileChunkExceedLimit = -0x4304,
  66. kApiFileMalformedChunk = -0x4305,
  67. kApiFileMalformedFileMeta = -0x4306,
  68. kApiFileMalformedSlice = -0x4307,
  69. kApiFileIsEmpty = -0x4308,
  70. kApiJoinFileExceedLimit = -0x4309, // 求交算法join文件太大
  71. kApiResultLineExceedLimit = -0x430a, // 求交算法结果行超出限制
  72. // data IO
  73. kApiFileOssObjectUploadFailed = -0x4400,
  74. kApiFileOssObjectDownloadFailed = -0x4401,
  75. kApiFileOssObjectNotFound = -0x4402,
  76. kApiFileOssObjectTooLarge = -0x4403,
  77. // data read & write
  78. kApiFileNonSupportedDecoder = -0x4500,
  79. kApiFileNonSupportedEncoder = -0x4501,
  80. kApiFileEndOfTfile = -0x4502, // tfile文件已读到结束
  81. // auxiliary
  82. kApiFileMalformedAuxiliary = -0x4600,
  83. kApiFileAuxiliaryCheckFailed = -0x4601,
  84. kApiFileNonSupportFileType = -0x4602,
  85. // runtime
  86. kApiFileStatusInvalid = -0x4700,
  87. // integrity
  88. kApiFileSliceRootHashError = -0x4800, // slice root hash 完整新校验失败
  89. kApiFileMetaHashError = -0x4801, // slice root hash 完整新校验失败
  90. };

CryptoErrorCode

我们会将所有接口返回码统一到 ApiErrorCode,此返回码会保留。

  1. // MYTF 密码操作相关接口返回值
  2. enum CryptoErrorCode: int {
  3. kSuccess = 1,
  4. kFail = 0,
  5. kInternalError = -0x0001,
  6. // 输入错误
  7. kPrivateKeyBufferError = -0x0002,
  8. kPublicKeyBufferError = -0x0003,
  9. kDigestBufferError = -0x0004,
  10. kDataBufferError = -0x0005,
  11. kSignatureBufferError = -0x0006,
  12. kInputDataError = -0x0007,
  13. // 过程错误
  14. kUnknownDigestName = -0x0008,
  15. kPrivateKeyError = -0x0009,
  16. kPublicKeyError = -0x000a,
  17. // 输出错误
  18. kOutputBufferError = -0x000b,
  19. // 授权信封错误
  20. kEnvelopeFieldError = -0x000c,
  21. kEnvelopeProofFieldError = -0x000d,
  22. kUnsupportType = -0x000e,
  23. kInvalidDid = -0x000f,
  24. kInvalidVC = -0x0010,
  25. kInvalidAuth = -0x0011,
  26. kSubjectNotMatch = -0x0012,
  27. kAuthNotMatch = -0x0013,
  28. kInvalidSignature = -0x0014,
  29. kInvalidDidProof = -0x0015,
  30. kInvalidCallerSig = -0x0016,
  31. // 其他
  32. kGasExhausted = -0x0017,
  33. };

常见问题

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的内存使用量,及时释放不需要的内存。优化示例如下:

  1. // 此处s占用10M空间,s2再次申请10M空间,则会内存超限
  2. std::string s(10 * 1024 * 1024, 'a');
  3. DoSomething(s);
  4. std::string s2(10 * 1024 * 1024, 'a');
  5. // 优化方式1: 我们使用{}将s的作用域限制一下,那么在}处,s就会被释放。s2就能正常申请内存
  6. {
  7. std::string s(10 * 1024 * 1024, 'a');
  8. DoSomething(s);
  9. }
  10. std::string s2(10 * 1024 * 1024, 'a');
  11. // 优化方式2:我们在s使用结束后及时清空s的内存。s2就能正常申请内存
  12. // 使用下面第3行所示的swap()方式清空s内存。注意,调用s.clear()并不会立即清空内存,推荐使用swap方式
  13. std::string s(10 * 1024 * 1024, 'a');
  14. DoSomething(s);
  15. std::string().swap(s);
  16. std::string s2(10 * 1024 * 1024, 'a');