可信计算应用开发

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

点击下载 macOS 版本

点击下载 Linux 版本

该编译工具链基于 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_DEBUGLOG_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
// 函数原型
// 解密信封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 算法的解密。支持数据一方加密后多方解密。

ecenvelope
// 原型
// 指定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 收到数据使用者的计算请求时,会校验使用者是否具有隐私数据的使用权限。

authenvelope
// 原型
// 数据拥有者的加密数据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_256Sm2Sm3SignSm2Sm3VerifySm2Sm3EncryptSm2Sm3DecryptSm4GcmEncryptSm4GcmDecrypt,示例如下:

// 函数原型
// 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

Base64EncodeBase64Decode 的示例如下:

// 原型
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');