cgo call, efficient, fast and stable, no memory collision

1. Background

Because many faster algorithms in cryptography are based on c or c ++ compilation, and the engineering is mainly based on go language, so here are some common problems and use cases of go calling c.

There are many strange ways to transfer, but if you want the best performance, pointer transfer is the main transfer method.

Some simple calculations can be directly written in c as .h for reference, but project deployment often has a large number of dependent libraries. If the deployment time on the server is too slow, there may also be network problems. So the best way is to compile all dependent libraries into dynamic libraries .so and .dylib for use by the deployer.

Second, cgo call

1. The cgo dependent library calls need to build the environment:

CGO_LDFLAGS

-L/Users/admin/Desktop/pir/pir_arm64/pir_cpp/cmake_arm -lpir

-L{path} -l{dynamic library}: such as libpir.so

DYLD_LIBRARY_PATH

/Users/admin/Desktop/pir/pir_arm64/pir_cpp/cmake_arm

Dynamic library location

CGO_CFLAGS

-I/Users/admin/Desktop/pir/pir_arm64/pir_cpp/thirdparty/pir

.h header file location

You can export global settings in the environment, or you can enable a single import before:

CGO_CFLAGS="-I${PIR_HOME}/pir_cpp/thirdparty/pir"
DYLD_LIBRARY_PATH="${PIR_HOME}/pir_cpp/build"
CGO_LDFLAGS="-L${PIR_HOME}/pir_cpp/build -lpir" go run main.go

Note: Drink two dependent libraries at the same time:

CGO_ENABLED=1 CGO_CFLAGS="-I${PIR_HOME}/pir_cpp/thirdparty/pir -I${GMSSL_HOME}/include"
CGO_LDFLAGS="-L${PIR_HOME}/pir_cpp/build -lpir2
-L${GMSSL_HOME}/lib -lgmcrypto -lgmssl"
go build -tags=jsoniter -ldflags "$(API_LINK_OPTIONS)" -o ${BIN_PATH}/${API_APP_NAME} ${API_CMD_PATH}

Do not add double quotes when configuring the panorama variable CGO_LDFLAGS in Goland.

2. How to call the c function and get the calculation result in go (there are many methods, and the most stable and fastest solution is introduced)

(1) The data structure is simple:

func OprfBlind(id []byte, key []byte) ([]byte, error) {

if len(key_receiver) != 32 {

return nil, ErrInvalidKey

}

if id_receiver == nil {

return nil, ErrEmptyParameter

}

size := C. size_t(32)

array := C. malloc(size)

defer C. free(array)

C.oprf_mul_value((*C.char)(unsafe.Pointer( & amp;id[0])), (*C.char)(unsafe.Pointer( & amp;key[0])), (*C. char)(array), (C.int(len(id)))) // can return an integer as the length of the ciphertext

ciphertext := C.GoBytes(unsafe.Pointer(array), C.int(32)) //Know that the returned data length is 32

return ciphertext, nil

}

(2) The structure is relatively complex, and the structure is used for transmission.

func Oprf2Key(ciphertexts_r []byte, key_sender []byte) ([]byte, error) {

if len(ciphertexts_r) == 0 {

return nil, ErrInvalidPoint

}

if len(key_sender) != 32 {

return nil, ErrInvalidKey

}

key_sender = string2Key(key_sender, 32)

cgodata := (*C.cgoData)(C.malloc(C.size_t(unsafe.Sizeof(C.cgoData{}))))

defer C. free(unsafe. Pointer(cgodata))

C.oprf_mul_A((*C.char)(unsafe.Pointer( & amp;ciphertexts_r[0])), (*C.char)(unsafe.Pointer( & amp;key_sender[0])), cgodata, (C .int)(len(ciphertexts_r)))

ciphertext := C.GoBytes(unsafe.Pointer(cgodata.data), C.int(32))

return ciphertext, nil

}

typedef struct {

   char *data;

   int data_len;

}cgoData;

#cgodata.data does not allocate memory for it in go, so we need to allocate it in c++:

int oprf_div_A(const char *ids, const char *A, cgoData *plaintext, int size) {

    myECC ecc;

    plaintext->data = new char [32];

   int flag = ecc.point_div(A, ids, plaintext->data);

   return flag;

}
func SealInit(NumberItems int, MaxSizeItem int) ([]unsafe. Pointer, error) {

if NumberItems <= 0 || MaxSizeItem <= 0 {

return nil, errors. New("sealGenerateQuery Error: NumberItems, SizePerItem should be >0")

}

cSealData := (*C.SealData)(C.malloc(C.size_t(unsafe.Sizeof(C.SealData{}))))

sealParams := make([]unsafe. Pointer, 2)

defer C.free(unsafe.Pointer(cSealData))

cSealData.poly_degree = C.int(4096)

cSealData.logt = C.int(20)

cSealData.number_of_items = C.int(NumberItems)

cSealData.size_per_item = C.int(MaxSizeItem % 10000)

sealParams[0] = C.seal_init(cSealData)

if MaxSizeItem > 10000 {

cSealData.size_per_item = C.int(10000)

sealParams[1] = C.seal_init(cSealData)

}

return sealParams, nil

}

(3) The pointer passes multiple parameters:

3. Bug fixes:

1. How to allocate memory:

___go_build_data_create_go(90164,0x16f6f3000) malloc: Heap corruption detected, free list is damaged at 0x6000002fc000

*** Incorrect guard value: 15577313418704380084

___go_build_data_create_go(90164,0x16f6f3000) malloc: *** set a breakpoint in malloc_error_break to debug

func EncryptElement(values []byte, key []byte) ([]byte, error) {

if len(values) == 0 || len(values) == 0 {

return nil, ErrEmptyParameter

}

if values == nil || key == nil {

return nil, ErrEmptyParameter

}

size := C.size_t(16 * int((len(values) + 31)/16)) //Allocate more memory! ! !

array := C.malloc(size) //Allocate memory and release \ can also be generated internally, it must be larger.

defer C. free(array)

C.encrypt_element((*C.char)(unsafe.Pointer( & amp;values[0])), (*C.char)(unsafe.Pointer( & amp;key[0])), (*C. char)(array), (C.int)(len(values)))

ciphertext := C.GoBytes(unsafe.Pointer(array), C.int(size-16))

return ciphertext, nil

}

//Whether external or internal, memory allocation must be remembered. 

2. The for loop is easy to lose

func SealDecrypt(sealParams []unsafe.Pointer, reply [][]byte, position int, keys SealKeys) ([]byte, error) {

// allocate memory for the MyData structure



if len(keys.SecretKey) <= 0 || len(keys.PublicKey) <= 0 || len(reply) <= 0 {

return nil, errors.New("sealDecrypt Error: SecretKey , PublicKey and Ciphertext is NULL")

}

var plaintexts []byte

for i := 0; i < len(reply); i ++ {

cSealData := (*C.SealData)(C.malloc(C.size_t(unsafe.Sizeof(C.SealData{}))))

cSealData.ele_index = C.int(position) //It is easy to lose when placed outside, it is recommended to assign once each time

cSealData.secret_key = (*C.char)(unsafe.Pointer( & amp;keys.SecretKey[0]))

cSealData.public_key = (*C.char)(unsafe.Pointer( & amp;keys.PublicKey[0]))

if i == len(reply)-1 {

cSealData.seal_params = sealParams[0]

} else {

cSealData.seal_params = sealParams[1]

}



cSealData.reply = (*C.char)(unsafe.Pointer( & amp;reply[i][0]))



// Call the C function and pass the pointer to the MyData structure

C.seal_decrypt(cSealData)

// Convert the data returned by C to Go's slice type

Plaintext := C.GoBytes(unsafe.Pointer(cSealData.plaintext), C.int(cSealData.plaintext_len))



plaintexts = append(plaintexts, Plaintext...)

C.free(unsafe.Pointer(cSealData)) //Clear immediately after use! When the amount of data is large, the memory will report an error!

}

return plaintexts, nil

}

Fourth, use cases

simple.h

//
// Created by ybx on 2023/7/21.
//

#ifndef CGO_EXEMPLE_SIMPLE_H
#define CGO_EXEMPLE_SIMPLE_H
#ifdef __cplusplus
extern "C" {
#endif

typedef struct {
    char *data;
    int data_len;
}cgo_simple_struct;

int swap_value(char *value1, char *value2);

int swap_value_struct(cgo_simple_struct *struct1, cgo_simple_struct *struct2);

int get_value(char *data, cgo_simple_struct *cgo);

#ifdef __cplusplus
}
#endif

#endif //CGO_EXEMPLE_SIMPLE_H

simple.cpp

//
// Created by admin on 2023/7/21.
//
#include <cstring>
#include "simple.h"
int swap_value(char *value1, char *value2){
    // swap the contents of two character arrays
    char temp[strlen(value1) + 1];
    strcpy(temp, value1);
    strcpy(value1, value2);
    strcpy(value2, temp);
    return 0; // return a flag indicating that the exchange was successful
}

int swap_value_struct(cgo_simple_struct *struct1, cgo_simple_struct *struct2) {
    // swap the pointers and lengths in the two structures
    char *temp_data = struct1->data;
    int temp_data_len = struct1->data_len;

    struct1->data = struct2->data;
    struct1->data_len = struct2->data_len;

    struct2->data = temp_data;
    struct2->data_len = temp_data_len;

    return 0; // return a flag indicating that the exchange was successful
}


int get_value(char *data, cgo_simple_struct *cgo) {
    // allocate memory for cgo->data and copy the contents of ids to it
    cgo->data = new char[len + 1];
    strncpy(cgo->data, data, cgo->data_len);

    // save the length of ids
    cgo->data_len = len;

    return 0; // Return a flag indicating that the copy was successful
}

cmakelist:

cmake_minimum_required(VERSION 3.25)
project(c++)

set(CMAKE_CXX_STANDARD 17)

add_executable(c++ main.cpp src/simple.cpp src/simple.h)
add_library(cgo SHARED src/simple.cpp src/simple.h)

cgo.go

package main

// #cgo LDFLAGS: -L./c++ /build -lcgo
// #cgo CFLAGS: -I./c++/src
/*
#include "simple.h"
#include <stdlib.h>
#include <string.h>
*/
import "C"

import (
"fmt"
"unsafe"
)

func main() {
// call the swap_value function
str1 := []byte("Hello")
str2 := []byte("World")
fmt.Printf("Before swap: struct1 = %s, struct2 = %s\\
", string(str1), string(str2))
C.swap_value((*C.char)(unsafe.Pointer( & amp;str1[0])), (*C.char)(unsafe.Pointer( & amp;str2[0])))
fmt.Printf("After swap: struct1 = %s, struct2 = %s\\
", string(str1), string(str2))

// call the get_value function
cgodata1 := (*C.cgo_simple_struct)(C.malloc(C.size_t(unsafe.Sizeof(C.cgo_simple_struct{}))))
defer C. free(unsafe. Pointer(cgodata1))
cgodata1.data_len = C.int(len(str1))

C.get_value((*C.char)(unsafe.Pointer( & amp;str1[0])), cgodata1)
getData := C.GoBytes(unsafe.Pointer(cgodata1.data), C.int(cgodata1.data_len))
fmt.Printf("After get: struct->data = %s, struct->data_len = %d\\
", string(getData), cgodata1.data_len)

cgodata2 := (*C.cgo_simple_struct)(C.malloc(C.size_t(unsafe.Sizeof(C.cgo_simple_struct{}))))
defer C. free(unsafe. Pointer(cgodata2))
cgodata2.data = (*C.char)(unsafe.Pointer( & amp;str2[0]))
cgodata2.data_len = C.int(len(str2))

fmt.Printf("After swap: struct1->data = %s, struct2->data = %s\\
", string(getData), string(str2))

C. swap_value_struct(cgodata1, cgodata2)
getData1 := C.GoBytes(unsafe.Pointer(cgodata1.data), C.int(cgodata1.data_len))
getData2 := C.GoBytes(unsafe.Pointer(cgodata1.data), C.int(cgodata1.data_len))

// print the exchanged result
fmt.Printf("After swap: struct1->data = %s, struct2->data = %s\\
", string(getData1), string(getData2))

}

result: