使用 CFFI 进行依赖注入?

Dependency injection with CFFI?

提问人:JamesTheAwesomeDude 提问时间:10/10/2023 最后编辑:JamesTheAwesomeDude 更新时间:10/10/2023 访问量:51

问:

我正在尝试制作一个基于 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 注入 python-cffi

评论


答:

0赞 Armin Rigo 10/10/2023 #1

为此,您可以提供回调 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