Trusted application development

更新时间:
复制 MD 格式

A trusted application (TAPP) is a WebAssembly (WASM) smart contract that holds a key and runs in a trusted execution environment (TEE). TAPP uses a subset of the C++ language, based on the C99/C++14 standard, as its contract language. To develop a TAPP, you first download and install the TAPP compiler, mytf.mycdt. You can then use the compiler to compile your TAPP code into WASM bytecode and install the bytecode into the MYTF trusted compute engine. The MYTF engine interprets and executes the WASM bytecode.

Write a TAPP

The following code shows the basic framework of a TAPP (sample.cpp):

// The basic library, which includes all MYTF library functions.
#include <mychainlib/contract.h>
// A third-party library. This example imports a JSON library.
#include <third_party/rapidjson/document.h>
#include <third_party/rapidjson/stringbuffer.h>
#include <third_party/rapidjson/writer.h>
// The namespace for basic MYTF library functions.
using namespace mychain;
// The main TAPP class. It is the same as a C++ class definition, but it inherits from Contract.
class MyTapp : public Contract {
public:
    // A TAPP function. It is the same as a C++ function definition, but you use the INTERFACE macro for externally callable interfaces.
    INTERFACE std::string Encode(const std::string& data) {
        std::string out;
        // Calls the Base64Encode API provided by the MYTF library.
        CryptoErrorCode err = Base64Encode(data, out);
        if (err != CryptoErrorCode::kSuccess) {
            // Currently prints to the MYTF log.
            print("failed to base64encode: %d", err);
            return std::to_string(err);
        }
        return out;
    }

    // A TAPP function. It is the same as a C++ function definition, but you use the INTERFACE macro for externally callable interfaces.
    INTERFACE int Add(int a, int b) {
        return a + b;
    }
};
// Defines externally callable functions using a macro.
INTERFACE_EXPORT(MyTapp, (Encode) (Add))

Download the compiler

The compiler supports Linux and macOS. The versions and download links are as follows:

Name

Version

Download link

mytf.mycdt

0.2.2.1

Download for macOS

Download for Linux

This toolchain is based on Clang. You can use mytf.mycdt to compile trusted application code into WASM bytecode. Then, you can use the MYTF software development kit (SDK) to install the TAPP into C3S MYTF. The MYTF trusted compute engine interprets and executes the WASM bytecode.

Install the compiler

After you download the compiler, extract the package to a directory, such as $HOME/mycdt. Then, set the PATH environment variable.

Note: The output of my++ --version depends on the installed version.

$ export PATH=$PATH:$HOME/mycdt/bin
$ my++ --version
my++ version mytf-0.2.2.1

Compile the application

The following example shows how to compile the trusted application sample.cpp.

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

After compilation, the WASM bytecode file sample.wasm is generated. You can use the MYTF SDK to install this bytecode into the MYTF trusted compute engine for interpretation and execution. For more information, see the SDK user guide.

API details

In addition to the C++ standard library, MYTF provides several library functions and APIs that you can use in a TAPP to improve usability and performance. The APIs are categorized as follows:

Infrastructure interfaces

MyAssert & Require

// Function prototype
// If (not expression), the program stops and writes the msg information to the Output.
// (Each TAPP call returns a return_val and an Output. The Output contains system-level information. For more information, see the SDK documentation.)
// (The Output is not described again in the following sections.)
void MyAssert(int expression, const std::string& msg);

// If (not condition), the program stops and writes the exception information to the Output.
// The return value is 0 and has no meaning.
int Require(bool condition, const std::string& exception);

// Both interfaces have existed since Mychain smart contracts and have few differences.

// Example
INTERFACE std::string Encode(const std::string& data) {
    std::string out;
    // Calls the Base64Encode API provided by the MYTF library.
    CryptoErrorCode err = Base64Encode(data, out);
    MyAssert(err == CryptoErrorCode::kSuccess, "failed to base64encode");
    return out;
}

LOG_*

These include LOG_DEBUG, LOG_INFO, and LOG_ERROR, which print debug information. Each output message is a new line. For example, LOG_DEBUG("%d", INT32_MAX); outputs "[DEBUG] 2147483647\n". The maximum length of each line is 512 bytes. Content that exceeds this length is truncated.

// Prototype
// The format is similar to printf in C99.
int LOG_DEBUG(const char* format, ...);
// Example
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("");              // 
}

During a TAPP call, LOG information is saved in a rotating log buffer. After the call ends, the buffer is returned to the caller in ExecuteRes.log. The log buffer size is currently set to 200 KB. If this limit is exceeded, the buffer is overwritten. For example, if you write 30,000 lines of 20 bytes each using LOG_DEBUG, only the last 10,000 lines are kept and returned to the caller. You can specify the log level when you install a TAPP:

enum TappLogLevel {
    kTappLogDebug = 0,
    kTappLogInfo  = 1,
    kTappLogError = 2,
    kTappLogDisable = 3,
};

The default level is kTappLogDisable, which outputs no logs. LOG operations below the specified level have no effect. For example, if you set the log level to kTappLogError when you install a TAPP, LOG_DEBUG and LOG_INFO in the TAPP do not print information.

Bin2Hex & Hex2Bin

// Function prototype
// Converts each byte into two bytes (hex mode). For example, "\0\255" becomes "00ff" or "00FF" (if uppercase = true).
std::string Bin2Hex(const std::string& input, bool uppercase = false);
// The reverse operation of the preceding function. It allows prefixes such as 0x or 0X.
std::string Hex2Bin(const std::string& input);

Envelope interfaces

EnvelopeOpen & EnvelopeBuild

Inside a TAPP, you can use this function to perform encryption and decryption based on ECIES-SECP256K1 or Chinese cryptographic algorithms. This function supports scenarios where multiple users encrypt their data. A third-party service platform can then merge the encrypted data from multiple parties and request TAPP execution without accessing the users' private data.

envelope

// Function prototype
// Decrypts the envelope to get the plain_data inside. For information about how to construct an envelope, see the SDK documentation.
CryptoErrorCode EnvelopeOpen(const std::string& envelope, std::string& plain_data);
// Constructs an envelope. It uses the pk and the algo algorithm to encrypt plain_data and get the encrypted envelope.
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);

// Example
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

Inside a TAPP, you can use this function to perform decryption based on the ECElgamal-SECP256K1 algorithm. This function supports scenarios where data is encrypted by one party and decrypted by multiple parties.

ecenvelope

// Prototype
// Specifies curve_type and prikey to decrypt the cipher and get the output.
CryptoErrorCode ECElgamalEnvelopeOpen(uint32_t curve_type, const std::string& prikey, const std::string& cipher, std::string& output);
// Example
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

In MYTF, each TAPP has a public-private key pair for encryption. The data owner must use the TAPP's public key to encrypt private data and construct an AuthEnvelope data ownership structure. This structure controls data access permissions and ensures that only this TAPP can decrypt the data. When a data owner needs to grant another person access to private data, the owner issues a Verifiable Credential (VC) to the data user. The VC specifies who can use the data and which TAPP functions they can execute. When the TAPP receives a compute request from a data user, it verifies whether the user has permission to use the private data.

authenvelope

// Prototype
// The data owner's encrypted data envelope. The caller provides the owner's authorization and their own identity proof (id_proof). Call this interface to get the plaintext data.
CryptoErrorCode AuthEnvelopeOpen(const std::string& envelope, const std::string& authorization, const std::string& id_proof, std::string& plain /* out */);
// Example
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;
}

Cryptography interfaces

RsaSign & RsaVerify

// Prototype
// Uses pri_key and the digest_name digest method to sign the data and get the signature.
CryptoErrorCode RsaSign(const std::string& pri_key, const std::string& digest_name, const std::string& data, std::string& sign);
// Uses pub_key and the digest_name digest method to verify the signature of the data.
CryptoErrorCode RsaVerify(const std::string& pub_key, const std::string& digest_name, const std::string& data, const std::string& sign);

// Example
INTERFACE std::vector<std::string> OrderVerify() {
    std::vector<std::string> ret;
    // Cryptography configuration
    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";
    // Get the signature
    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";

    // Verify the signature
    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

This TAPP function uses the TAPP's private key to sign the data. The signature algorithm is SECP256K1. The internal implementation uses the secp256k1 algorithm optimized by Bitcoin. Ensure that the signature verification endpoint is aligned with this algorithm.

  • Function prototype

/*
Function: Uses the current TAPP's private key to sign the data. The digest algorithm is SHA256. The signature algorithm curve is curve_name, which defaults to SECP256K1.
Return value: The execution result, which is an int enumeration type. For explanations of specific error codes, see the sections below.
Parameters:
  data: Input parameter. The data to be signed.
  sign: Output parameter. The resulting signature.
  curve_name: Optional input parameter. The curve used by the signature algorithm. Options are ECDSA_RAW_SECP256K1_KEY or 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

// Prototype
// Calculates the SHA256 digest of msg to get the hash.
CryptoErrorCode Sha256(const std::string& msg, std::string& hash);

Hmac

/* Prototype
 * Calculates the HMAC. It uses the md_type digest algorithm and the key to calculate the HMAC of msg and get the result mac.
 * The digest algorithm types are as follows:
    enum MdType: int {
        kSha1 = 0,
        kSha256 = 1,
    }; 
*/
CryptoErrorCode Hmac(const MdType& md_type, const std::string& msg, const std::string& key, std::string& mac);

// Example
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");

Chinese cryptographic algorithm interfaces

This interface supports Sm3_256, Sm2Sm3Sign, Sm2Sm3Verify, Sm2Sm3Encrypt, Sm2Sm3Decrypt, Sm4GcmEncrypt, and Sm4GcmDecrypt. The following is an example:

// Function prototype
// The const parameter msg is an input parameter. The non-const parameter hash is the result output parameter. Uses the SM3 256 algorithm to calculate the digest hash of msg.
// Unless otherwise specified, all following const parameters are input parameters, and non-const parameters are result output parameters. The function and parameter names are self-explanatory.
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);

// Example
// Comprehensively tests all SM interfaces.
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;
}

Symmetric encryption and decryption interfaces

This interface supports AesGcmDecrypt (AES/GCM/NoPadding decryption algorithm, supports key lengths of 128, 192, and 256 bits), AesEcbDecrypt (AES/ECB/PKCS5Padding decryption algorithm, supports key lengths of 128, 192, and 256 bits), AesGcmEncrypt (AES/GCM/NoPadding encryption algorithm, supports key lengths of 128, 192, and 256 bits), and AesEcbEncrypt (AES/ECB/PKCS5Padding encryption algorithm, supports key lengths of 128, 192, and 256 bits). For more information, see the following code:

/**
    Function prototype: AesGcmDecrypt
    Parameter description:
           key: The encryption key. Valid lengths are 16, 24, or 32 bytes.
           iv: The initialization vector. 12 bytes.
           aad: Additional Authenticated Data.
           cipher: The ciphertext.
           plain[out]: The decrypted plaintext.
    Return value:
           A CryptoErrorCode error code.
*/           
CryptoErrorCode AesGcmDecrypt(const std::string& key, const std::string& iv, const std::string& aad, const std::string& cipher, std::string& plain);

// Code example
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;
    }

/**
    Function prototype: AesGcmEncrypt
    Parameter description:
           key: The encryption key. Valid lengths are 16, 24, or 32 bytes.
           iv: The initialization vector. 12 bytes.
           aad: Additional Authenticated Data.
           plain: The plaintext.
           cipher[out]: The encrypted ciphertext.
    Return value:
           A CryptoErrorCode error code.
*/ 
CryptoErrorCode AesGcmEncrypt(const std::string& key, const std::string& iv, const std::string& aad, const std::string& plain, std::string& cipher);

// Example code
    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;
    }

/**
    Function prototype: AesEcbDecrypt
    Parameter description:
           key: The encryption key. Valid lengths are 16, 24, or 32 bytes.
           cipher: The ciphertext.
           plain[out]: The decrypted plaintext.
    Return value:
           A CryptoErrorCode error code.
*/  
CryptoErrorCode AesEcbDecrypt(const std::string& key, const std::string& cipher, std::string& plain);

// Example code
    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;
    }

/**
    Function prototype: AesEcbEncrypt
    Parameter description:
           key: The encryption key. Valid lengths are 16, 24, or 32 bytes.
           plain: The plaintext.
           cipher: The encrypted ciphertext.
    Return value:
           A CryptoErrorCode error code.
*/ 
CryptoErrorCode AesEcbEncrypt(const std::string& key, const std::string& plain, std::string& cipher);

// Example code
    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

The following is an example of Base64Encode and Base64Decode:

// Prototype
CryptoErrorCode Base64Encode(const std::string& input, std::string& output);
CryptoErrorCode Base64Decode(const std::string& input, std::string& output);

Time processing interfaces

SDK version requirement: 0.2.2.2 or later.

  • Convert a time string to a UNIX timestamp: strptime

    Note

    You must include the <time.h> header file.

    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)
        // Converts a local time string to a local time tm structure based on the specified time zone (UTC offset).
        // Currently supported time zones:
        //             CHINA_STANDARD_TIME_TZ_OFFSET
        //             GREENWICH_MEAN_TIME_TZ_OFFSET
        // Or, you can directly provide the difference between the local time zone and UTC.
        char* s = strptime_tz(time_string.c_str(), fmt.c_str(), utc_offset, &tm);
        MyAssert(s != NULL, "strptime");
        // Convert to a timestamp.
        time_t t = timegm(&tm);
        return (long)t;
    }

SDK example code:

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());
  • Convert a UNIX timestamp to a time string: strftime

    Note

    You must include the <time.h> header file.

#include <time.h>

INTERFACE String StrftimeEval(long ts, String fmt, int utc_offset) {

      // The length is 128. This is an example value. Set it to the actual length of the formatted string.
      // The specified length can be greater than the actual length.
      // If it is less than the actual length, the string is truncated.
      const size_t buffer_size = 128;
      char buffer[buffer_size];

      struct tm *pt;

      // UNIX timestamp
      time_t mytime = (time_t)(ts);

      // struct tm *localtime(const time_t *t, int utc_hour_off)
      // Converts a timestamp to a local time zone tm structure.
      // Currently supported time zones: CHINA_STANDARD_TIME_TZ_OFFSET (+8)
      //             GREENWICH_MEAN_TIME_TZ_OFFSET (0)
      // Or, you can directly provide the difference between the local time zone and UTC, from -12 to 12.
      // If the conversion fails, it returns 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);
      // Convert to a formatted string.
      // fmt can be "%Y-%m-%d %H:%M:%S".
      // For more format specifiers, see https://www.man7.org/linux/man-pages/man3/strftime.3.html.
      // If the conversion fails, it returns 0.
      size_t len = strftime(buffer, buffer_size, fmt.c_str(), pt);
      MyAssert(len != 0, "strftime");

      return String{buffer, len};
    }

SDK usage code:

// Send an end-to-end encrypted request.
        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());

Third-party libraries

The MYCDT compiler integrates several third-party libraries. Note that third-party libraries are provided as compiler library functions. This means the functions are compiled into WASM bytecode, which increases the WASM file size and limits the running speed because of interpretation. In contrast, the APIs described in the preceding sections are integrated externally from WASM and are natively implemented inside SGX. They are not part of the WASM compilation process.

The following third-party libraries are supported:

Return value definitions

ApiErrorCode

This topic describes the unified return codes for TAPP library API calls.

enum ApiErrorCode: int {
    kApiSuccess               = 1,
    kApiFail                  = 0,
    kApiInternalError         = -0x0001,

    // ------------------------------------------
    //  COMMON ERROR CODE: [-0x2000, -0x2fff]
    // ------------------------------------------
    kApiDoNotUseSyncInterface       = -0x2000,

    // ------------------------------------------
    //  CRYPTO RELATED ERROR CODE: [-0x0002, -0x1fff]
    // ------------------------------------------
    // Crypto ErrorCode
    // Input errors
    kApiPrivateKeyBufferError = -0x0002,
    kApiPublicKeyBufferError  = -0x0003,
    kApiDigestBufferError     = -0x0004,
    kApiDataBufferError       = -0x0005,
    kApiSignatureBufferError  = -0x0006,
    kApiInputDataError        = -0x0007,
    // Process errors
    kApiUnknownDigestName     = -0x0008,
    kApiPrivateKeyError       = -0x0009,
    kApiPublicKeyError        = -0x000a,
    // Output errors
    kApiOutputBufferError     = -0x000b,
    // Authorization envelope errors
    kApiEnvelopeFieldError      = -0x000c,
    kApiEnvelopeProofFieldError = -0x000d,
    kApiUnsupportType           = -0x000e,
    kApiInvalidDid              = -0x000f,
    kApiInvalidVC               = -0x0010,
    kApiInvalidAuth             = -0x0011,
    kApiSubjectNotMatch         = -0x0012,
    kApiAuthNotMatch            = -0x0013,
    kApiInvalidSignature        = -0x0014,
    kApiInvalidDidProof         = -0x0015,
    kApiInvalidCallerSig        = -0x0016,
    // Others
    kApiGasExhausted            = -0x0017,
    // Incorrect input parameter 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]
    // ------------------------------------------
    // Permissions
    kApiFilePermissionInvalid = -0x4000,

    // File handles
    kApiFileNotOpened = -0x4100,   // tfile handle is not open
    kApiFileOpenedExceedTotalLimit = -0x4101,
    kApiFileOpenedExceedRequestLimit = -0x4102,

    // data address
    kApiFileNonSupportedFileSourceType = -0x4200, // Incorrect file meta address format. The valid format is xx.meta.hash.
    kApiFileMetaAddressFormatError = -0x4201, // Incorrect file meta address format. The valid format is 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,   // The join file for the intersection algorithm is too large.
    kApiResultLineExceedLimit = -0x430a, // The number of result rows for the intersection algorithm exceeds the limit.

    // data IO
    kApiFileOssObjectUploadFailed = -0x4400,
    kApiFileOssObjectDownloadFailed = -0x4401,
    kApiFileOssObjectNotFound = -0x4402,
    kApiFileOssObjectTooLarge = -0x4403,

    // data read & write
    kApiFileNonSupportedDecoder = -0x4500,
    kApiFileNonSupportedEncoder = -0x4501,
    kApiFileEndOfTfile = -0x4502,   // Reached the end of the tfile.

    // auxiliary
    kApiFileMalformedAuxiliary = -0x4600,
    kApiFileAuxiliaryCheckFailed = -0x4601,
    kApiFileNonSupportFileType = -0x4602,

    // runtime
    kApiFileStatusInvalid = -0x4700,

    // integrity
    kApiFileSliceRootHashError = -0x4800, // Slice root hash integrity check failed.
    kApiFileMetaHashError = -0x4801, // Slice root hash integrity check failed.

};

CryptoErrorCode

All API return codes will be unified as ApiErrorCode, which will be retained.

// Return values for MYTF cryptography-related APIs
enum CryptoErrorCode: int {
   kSuccess               = 1,
   kFail                  = 0,
   kInternalError         = -0x0001,
   // Input errors
   kPrivateKeyBufferError = -0x0002,
   kPublicKeyBufferError  = -0x0003,
   kDigestBufferError     = -0x0004,
   kDataBufferError       = -0x0005,
   kSignatureBufferError  = -0x0006,
   kInputDataError        = -0x0007,
   // Process errors
   kUnknownDigestName     = -0x0008,
   kPrivateKeyError       = -0x0009,
   kPublicKeyError        = -0x000a,
   // Output errors
   kOutputBufferError     = -0x000b,
   // Authorization envelope errors
   kEnvelopeFieldError      = -0x000c,
   kEnvelopeProofFieldError = -0x000d,
   kUnsupportType           = -0x000e,
   kInvalidDid              = -0x000f,
   kInvalidVC               = -0x0010,
   kInvalidAuth             = -0x0011,
   kSubjectNotMatch         = -0x0012,
   kAuthNotMatch            = -0x0013,
   kInvalidSignature        = -0x0014,
   kInvalidDidProof         = -0x0015,
   kInvalidCallerSig        = -0x0016,
   // Others
   kGasExhausted            = -0x0017,
};

FAQ

C++ standard support

For security and auditing purposes, we recommend that you do not use the following infrastructure:

  • Pointers and references: Out-of-bounds pointer behavior is one of the most elusive issues in C99/C++14. You must be extra careful during audits.

  • Arrays: Out-of-bounds array access is a common and hard-to-debug error in C++. The TAPP language provides std::vector as an orthogonal alternative.

  • enum, enum class, and union: The memory layout of enum (weakly-typed enumeration), enum class (strongly-typed enumeration), and union is consistent with standard C++. However, similar to arrays, type checking and bounds checking are difficult to enforce effectively.

  • The TAPP language supports features such as overloading, templates, and inheritance, which can be used in combination.

Standard library support

  • Memory management operations, such as malloc/free and new/delete, have been rewritten to ensure security.

  • Process control operations, such as abort/exit, have been rewritten to ensure security and should not be used in a TAPP.

  • I/O operations included in iostream/cstdio are not allowed. Instead, the TAPP language provides a print interface that behaves like printf in C++ for local debugging and output.

  • The serialization behavior for C++14 standard library types other than std::vector and std::string, and for the serializable types described in Serializable data types, is not strictly defined. Therefore, these types cannot be passed as function parameters.

System call support

Unsupported call types:

  • Network: DNS, TCP, UDP, IPv6, Ethernet, sockets, epoll, poll, and select.

  • I/O: File operations, device ioctl, multi-buffer read/write, and memory mapping.

  • System kernel: Process analysis (ptrace), disk usage, system file usage statistics, and Linux file system operations.

  • System scheduling: Dynamic loading, cache control, and restart.

  • Inter-process communication: Messaging, semaphores, shared memory, and signal handling.

  • Thread scheduling: Threads, locks, user-level in-process multi-threaded context switching, and timers such as sleep and wait.

  • System access control: User accounts and file permissions.

  • Other: Archiving, complex numbers, regular expressions, terminal emulation (mount), system call entry (syscall), time, random numbers, encoding conversion, system logs, and "long double" support.

Compiler option support

The compiler uses the following compilation parameters to restrict certain C++ features. Do not use these disabled features when you write a TAPP.

  • -fno-cfl-aa: Disables CFL-AA (alias analysis).

  • -fno-elide-constructors: Disables copy elision for copy constructors.

  • -fno-lto: Disables link-time optimization.

  • -fno-rtti: Disables Run-Time Type Information (RTTI).

  • -fno-exceptions: Disables exceptions.

  • -fno-threadsafe-statics: Disables thread-safe initialization of static local variables.

Compiler installation or usage fails

The compiler has been tested on Ubuntu, CentOS, AliOS, and macOS. However, compilation issues may occur in some Ubuntu environments. To resolve these issues: • A name conflict may occur because the compiler my++ shares its name with the Mychain compiler. Run which my++ to check if another my++ exists in your path. Then, run my++ --version to verify that you are using the correct compiler. The output should be "my++ version mytf-VERSION(commitid)". • Move your source file, such as xx.cpp, to the $MYCDT_INSTALL_HOME/bin/ folder. Then, run the my++ xx.cpp -o xx.wasm command from this folder to compile the file.

Why does the result return "abort called"?

This error typically occurs when a TAPP program calls abort(). For example, a JSON library might call abort when it encounters an illegal field access. If this error occurs, carefully check your TAPP program.

Why does the result return "res[8450]: out of bounds memory access"?

A TAPP has a memory limit of 16 MB. An error is returned if this limit is exceeded. If this error occurs, check your TAPP's memory usage and release any unneeded memory promptly. The following are some optimization examples:

// Here, s occupies 10 MB of space. If s2 requests another 10 MB, it will exceed the memory limit.
std::string s(10 * 1024 * 1024, 'a');
DoSomething(s);
std::string s2(10 * 1024 * 1024, 'a');


// Optimization 1: Use {} to limit the scope of s. When the scope ends at }, s is released. Then, s2 can request memory normally.
{
    std::string s(10 * 1024 * 1024, 'a');
    DoSomething(s);
}
std::string s2(10 * 1024 * 1024, 'a');


// Optimization 2: Clear the memory of s after you finish using it. Then, s2 can request memory normally.
// Use the swap() method as shown in line 3 to clear the memory of s. Note that calling s.clear() does not immediately clear the memory. We recommend that you use the swap method.
std::string s(10 * 1024 * 1024, 'a');
DoSomething(s);
std::string().swap(s);
std::string s2(10 * 1024 * 1024, 'a');