提问人:JamesTheAwesomeDude 提问时间:10/10/2023 最后编辑:JamesTheAwesomeDude 更新时间:10/10/2023 访问量:51
使用 CFFI 进行依赖注入?
Dependency injection with CFFI?
问:
我正在尝试制作一个基于 CFFI 的玩具/概念验证 Python 库,公开经典 McEliece KEM 版本 6960119f 的 PQClean 实现。
该文档指出,您必须“提供实现使用的任何常见加密算法的实例化”。这是真的,因为以下代码:
from cffi import FFI
ffibuilder = FFI()
ffibuilder.cdef("""
int PQCLEAN_MCELIECE6960119F_CLEAN_crypto_kem_enc(
uint8_t *c,
uint8_t *key,
const uint8_t *pk
);
int PQCLEAN_MCELIECE6960119F_CLEAN_crypto_kem_dec(
uint8_t *key,
const uint8_t *c,
const uint8_t *sk
);
int PQCLEAN_MCELIECE6960119F_CLEAN_crypto_kem_keypair
(
uint8_t *pk,
uint8_t *sk
);
""")
ffibuilder.set_source("_libmceliece6960119f", """
#include "api.h"
""",
library_dirs=["Lib/PQClean/crypto_kem/mceliece6960119f/clean"],
include_dirs=["Lib/PQClean/crypto_kem/mceliece6960119f/clean"],
libraries=["libmceliece6960119f_clean"])
if __name__ == "__main__":
import os
assert 'x64' == os.environ['VSCMD_ARG_TGT_ARCH'] == os.environ['VSCMD_ARG_HOST_ARCH']
ffibuilder.compile(verbose=True)
#^ Code crashes here
from _libmceliece6960119f import lib as libmceliece6960119f
...
由于缺少 和 的实现,产生 MSVC 错误 1120:void shake256(uint8_t *output, size_t outlen, const uint8_t *input, size_t inlen);
int randombytes(uint8_t *output, size_t n);
Creating library .\Release\_libmceliece6960119f.cp311-win_amd64.lib and object .\Release\_libmceliece6960119f.cp311-win_amd64.exp
LINK : warning LNK4098: defaultlib 'LIBCMT' conflicts with use of other libs; use /NODEFAULTLIB:library
libmceliece6960119f_clean.lib(operations.obj) : error LNK2001: unresolved external symbol shake256
libmceliece6960119f_clean.lib(operations.obj) : error LNK2001: unresolved external symbol PQCLEAN_randombytes
libmceliece6960119f_clean.lib(encrypt.obj) : error LNK2001: unresolved external symbol PQCLEAN_randombytes
.\_libmceliece6960119f.cp311-win_amd64.pyd : fatal error LNK1120: 2 unresolved externals
…
Traceback (most recent call last):
File "C:\Users\████\Documents\code\███\cffi_compile.py", line 34, in <module>
ffibuilder.compile(verbose=True)
File "C:\Users\████\.venvs\███\Lib\site-packages\cffi\api.py", line 725, in compile
return recompile(self, module_name, source, tmpdir=tmpdir,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\████\.venvs\███\Lib\site-packages\cffi\recompiler.py", line 1564, in recompile
outputfilename = ffiplatform.compile('.', ext,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\████\.venvs\███\Lib\site-packages\cffi\ffiplatform.py", line 20, in compile
outputfilename = _build(tmpdir, ext, compiler_verbose, debug)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\████\.venvs\███\Lib\site-packages\cffi\ffiplatform.py", line 54, in _build
raise VerificationError('%s: %s' % (e.__class__.__name__, e))
cffi.VerificationError: LinkError: command 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools\\VC\\Tools\\MSVC\\14.37.32822\\bin\\HostX86\\x64\\link.exe' failed with exit code 1120
我不想在编译时为这些函数中的任何一个选择实现。我想强制这个库的使用者在运行时早期提供一个,理想情况下是作为 Python 函数(这两个函数都不需要实现任何流式处理功能,因为套件显然不需要该套件。Callable[[bytes], bytes], Callable[[int], bytes]
shake256_inc_…
PQCLEAN_MCELIECE6960119F_crypto_kem_…
这怎么能做到?
答:
为此,您可以提供回调 Python 代码的 C 实现,但要以允许被重写的方式编写该 Python 代码。
这应该足够了:
ffibuilder.cdef("""
...same stuff as before...
extern "Python+C" void shake256(uint8_t *output, size_t outlen, const uint8_t *input, size_t inlen);
extern "Python+C" int randombytes(uint8_t *output, size_t n);
""")
它会导致这两个函数作为 C 代码发出,编译器应该在其中找到它们。但是这两个函数的编写方式是它们回调 Python 代码。在运行时,该 Python 代码必须提供两个全局函数:
@ffi.def_extern()
def shake256(output_ptr, outlen, input_ptr, inlen):
xxx
@ffi.def_extern()
def randombytes(output_ptr, n):
xxx
您可以根据需要实施。例如,在库的 Python 代码中,您可以编写如下内容:
def register_randombytes(fn):
"""Call this global function from the code using this library.
fn(number_of_bytes) -> bytes
"""
global _my_randombytes
_my_randombytes = fn
@ffi.def_extern()
def randombytes(output_ptr, n):
rbytes = _my_randombytes(n)
if len(rbytes) > n:
raise ValueError("User randombytes function returned too much data!")
ffi.memmove(output_ptr, rbytes, len(rbytes))
return len(rbytes)
它不是一个非常灵活的 API,因为用户需要提供一个单一的全局函数。多个用户相互覆盖。但是很难做得更好,因为 C API 没有为回调提供泛型参数。void *data
评论