Skip to content

Commit

Permalink
implement python interface for sign and gimli_hash
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisnc committed Aug 11, 2020
1 parent 896b6f7 commit 8851d3d
Show file tree
Hide file tree
Showing 18 changed files with 379 additions and 0 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/.eggs/
/.sconsign.dblite
/build/
/dist/
/tags
*.egg-info/
__pycache__/
28 changes: 28 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
FROM ubuntu:18.04

RUN apt-get update \
&& apt-get install -y software-properties-common \
&& add-apt-repository ppa:deadsnakes/ppa

RUN apt-get update \
&& apt-get install -y \
clang \
gcc-arm-none-eabi \
gcc-multilib \
git \
llvm \
locales \
python3-pip \
python3.6-dev \
python3.7-dev \
scons

RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

RUN python3 -m pip install --upgrade pip
RUN python3 -m pip install virtualenv

WORKDIR /src
3 changes: 3 additions & 0 deletions black-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

black --check --include "\\.pyi?$|SCons" --exclude "/(\\.git|venv.*|build|.eggs)/" py ffibuilder.py setup.py
26 changes: 26 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/bash

set -xe

virtualenv -p python3.7 venv_black
source venv_black/bin/activate
pip install black==19.10b0
./black-check.sh
deactivate

scons --jobs "$(nproc)"

function pybuild {
version="python$1"
venv="venv$1"
virtualenv -p "$version" "$venv"
source "$venv/bin/activate"
pip install . pytest
pytest --verbose --color=yes test
python setup.py sdist bdist_wheel
deactivate
rm -rf "test/__pycache__"
}

pybuild 3.6
pybuild 3.7
23 changes: 23 additions & 0 deletions docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/bash

set -x

name="lithium"
image="$name-builder"

docker build --tag "$image" .

cidfile="$(mktemp -u cidfile.XXXXXX)"

docker run --interactive --tty \
--volume "$(pwd):/src/$name" \
--cidfile "$cidfile" \
--cap-add SYS_PTRACE \
"$image" \
"bash" "-c" \
"git clone $name $name-docker && cd $name-docker && ./build.sh"

container="$(cat "$cidfile")"
docker cp "$container:/src/$name-docker/dist" "dist"
docker rm "$container"
rm -f "$cidfile"
47 changes: 47 additions & 0 deletions ffibuilder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import cffi
import codecs

ffibuilder = cffi.FFI()

c_headers = [
"lithium/gimli.h",
"lithium/gimli_state.h",
"lithium/gimli_hash.h",
"lithium/sign.h",
]

ffibuilder.set_source(
"lithium._lithium",
"\n".join(["#include <%s>" % h for h in c_headers]),
include_dirs=["include"],
sources=[
"src/gimli.c",
"src/gimli_common.c",
"src/gimli_hash.c",
"src/fe.c",
"src/x25519.c",
"src/sign.c",
],
)

cdef = ""

for h in c_headers:
with codecs.open("include/%s" % h, encoding="utf-8") as f:
cdef_active = False
for line in f.read().splitlines():
if "cffi:begin" in line:
cdef_active = True
if "cffi:end" in line:
cdef_active = False
if cdef_active:
cdef += line + "\n"

cdef += """
extern "Python+C" void lith_random_bytes(unsigned char *buf, size_t len);
"""

ffibuilder.cdef(cdef.encode("ascii", "ignore").decode())

if __name__ == "__main__":
ffibuilder.compile(verbose=True)
4 changes: 4 additions & 0 deletions include/lithium/gimli.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@

#include <stdint.h>

/* cffi:begin */

#define GIMLI_WORDS 12

void gimli(uint32_t *state);

/* cffi:end */

#endif /* LITHIUM_GIMLI_H */
4 changes: 4 additions & 0 deletions include/lithium/gimli_hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include <stddef.h>

/* cffi:begin */

#define GIMLI_HASH_DEFAULT_LEN 32

typedef gimli_state gimli_hash_state;
Expand All @@ -19,4 +21,6 @@ void gimli_hash_final(gimli_hash_state *g, unsigned char *output, size_t len);
void gimli_hash(unsigned char *output, size_t output_len,
const unsigned char *input, size_t input_len);

/* cffi:end */

#endif /* LITHIUM_GIMLI_HASH_H */
4 changes: 4 additions & 0 deletions include/lithium/gimli_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@
#include <stddef.h>
#include <stdint.h>

/* cffi:begin */

typedef struct
{
uint32_t state[GIMLI_WORDS];
size_t offset;
} gimli_state;

/* cffi:end */

#endif /* LITHIUM_GIMLI_STATE_H */
4 changes: 4 additions & 0 deletions include/lithium/sign.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include <stdbool.h>

/* cffi:begin */

#define LITH_SIGN_LEN 64
#define LITH_SIGN_PUBLIC_KEY_LEN 32
#define LITH_SIGN_SECRET_KEY_LEN 64
Expand Down Expand Up @@ -49,4 +51,6 @@ bool lith_sign_verify(const unsigned char sig[LITH_SIGN_LEN], const void *msg,
size_t len,
const unsigned char public_key[LITH_SIGN_PUBLIC_KEY_LEN]);

/* cffi:end */

#endif /* LITHIUM_SIGN_H */
4 changes: 4 additions & 0 deletions py/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import gimli_hash
from . import sign

from . import _random
8 changes: 8 additions & 0 deletions py/_random.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from lithium._lithium import ffi

from secrets import token_bytes


@ffi.def_extern()
def lith_random_bytes(buf, n):
ffi.memmove(buf, token_bytes(n), n)
35 changes: 35 additions & 0 deletions py/gimli_hash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from lithium._lithium import ffi, lib


class GimliHash:
"""
An object for incrementally hashing a message.
"""

def __init__(self):
self._hash_state = ffi.new("gimli_hash_state *")
lib.gimli_hash_init(self._hash_state)

def update(self, data):
"""
Feed new data to be hashed.
"""
buf = ffi.from_buffer("unsigned char[%s]" % len(data), data)
lib.gimli_hash_update(self._hash_state, buf, len(buf))

def final(self, n=lib.GIMLI_HASH_DEFAULT_LEN):
"""
Emit an n-byte hash of the data given to update.
"""
buf = ffi.new("unsigned char[%s]" % n)
lib.gimli_hash_final(self._hash_state, buf, n)
return bytes(buf)


def gimli_hash(data, n=lib.GIMLI_HASH_DEFAULT_LEN):
"""
Compute an n-byte GimliHash of data in one shot.
"""
g = GimliHash()
g.update(data)
return g.final(n)
97 changes: 97 additions & 0 deletions py/sign.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from lithium._lithium import ffi, lib

_sig_cdecl = "unsigned char[LITH_SIGN_LEN]"
_secret_key_cdecl = "unsigned char[LITH_SIGN_SECRET_KEY_LEN]"
_public_key_cdecl = "unsigned char[LITH_SIGN_PUBLIC_KEY_LEN]"
_prehash_cdecl = "unsigned char[LITH_SIGN_PREHASH_LEN]"


class Sign:
"""
An object for computing or verifying a lithium signature.
"""

def __init__(self):
self._sign_state = ffi.new("lith_sign_state *")
lib.lith_sign_init(self._sign_state)

def update(self, data):
"""
Feed new data to the signature computation.
"""
buf = ffi.from_buffer(data)
lib.lith_sign_update(self._sign_state, buf, len(buf))

def final_create(self, secret_key):
"""
Create a signature over the data given to update.
"""
sk_buf = ffi.from_buffer(_secret_key_cdecl, secret_key)
sig_buf = ffi.new(_sig_cdecl)
lib.lith_sign_final_create(self._sign_state, sig_buf, sk_buf)
return bytes(sig_buf)

def final_verify(self, sig, public_key):
"""
Verify a signature over the data given to update.
"""
sig_buf = ffi.from_buffer(_sig_cdecl, sig)
pk_buf = ffi.from_buffer(_public_key_cdecl, public_key)
return lib.lith_sign_final_verify(self._sign_state, sig_buf, pk_buf)

def final_prehash(self):
"""
Compute a prehash blob from the data given to update, to be signed or verified separately.
"""
prehash_buf = ffi.new(_prehash_cdecl)
lib.lith_sign_final_prehash(self._sign_state, prehash_buf)
return bytes(prehash_buf)


def keygen():
"""
Generate a public and secret keypair.
"""
pk_buf = ffi.new(_public_key_cdecl)
sk_buf = ffi.new(_secret_key_cdecl)
lib.lith_sign_keygen(pk_buf, sk_buf)
return (bytes(pk_buf), bytes(sk_buf))


def create_from_prehash(prehash, secret_key):
"""
Create a signature from a prehash blob generated by Sign.final_prehash.
"""
sk_buf = ffi.from_buffer(_secret_key_cdecl, secret_key)
prehash_buf = ffi.from_buffer(_prehash_cdecl, prehash)
sig_buf = ffi.new(_sig_cdecl)
lib.lith_sign_create_from_prehash(sig_buf, prehash_buf, sk_buf)
return bytes(sig_buf)


def verify_prehash(sig, prehash, public_key):
"""
Verify a signature on a prehash blob generated by Sign.final_prehash.
"""
sig_buf = ffi.from_buffer(_sig_cdecl, sig)
prehash_buf = ffi.from_buffer(_prehash_cdecl, prehash)
pk_buf = ffi.from_buffer(_public_key_cdecl, public_key)
return lib.lith_sign_verify_prehash(sig_buf, prehash_buf, pk_buf)


def create(data, secret_key):
"""
Create a signature over data.
"""
s = Sign()
s.update(data)
return s.final_create(secret_key)


def verify(data, sig, public_key):
"""
Verify a signature over data.
"""
s = Sign()
s.update(data)
return s.final_verify(sig, public_key)
Loading

0 comments on commit 8851d3d

Please sign in to comment.