Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.pem
dist/
12 changes: 12 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[submodule "firmware/MaixPy"]
path = firmware/MaixPy
url = https://github.com/selfcustody/MaixPy
[submodule "vendor/embit"]
path = vendor/embit
url = https://github.com/diybitcoinhardware/embit
[submodule "vendor/urtypes"]
path = vendor/urtypes
url = https://github.com/selfcustody/urtypes
[submodule "vendor/foundation-ur-py"]
path = vendor/foundation-ur-py
url = https://github.com/selfcustody/foundation-ur-py
211 changes: 211 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# The MIT License (MIT)

# Copyright (c) 2021-2023 Krux contributors

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

# syntax=docker/dockerfile:1

############
# build-base
# install kendryte (k210), cmake and python dependencies
############
FROM gcc:9.5.0-bullseye AS build-base

RUN apt-get update -y && \
apt-get install --no-install-recommends -y -q \
wget \
tar \
zip \
unzip \
build-essential \
libtool \
autoconf \
automake \
autotools-dev \
curl \
libmpc-dev \
libmpfr-dev \
libgmp-dev \
gawk \
bison \
flex \
texinfo \
gperf \
libtool \
patchutils \
bc \
zlib1g-dev \
libexpat-dev \
libisl-dev \
python3 \
python3-pip \
python3-setuptools

RUN mkdir -p /opt && \
GIT_TERMINAL_PROMPT=0 \
git clone --depth 1 --branch v8.2.0-20190409 https://github.com/kendryte/kendryte-gnu-toolchain

RUN cd kendryte-gnu-toolchain && \
sed -i 's|https://github.com/bminor/binutils-gdb.git|https://github.com/riscvarchive/riscv-binutils-gdb.git|' .gitmodules && \
git submodule sync && \
git submodule update \
--init \
--recursive \
--depth 1



RUN cd kendryte-gnu-toolchain && \
export PATH=$PATH:/opt/kendryte-toolchain/bin && \
./configure --prefix=/opt/kendryte-toolchain --with-cmodel=medany --with-arch=rv64imafc --with-abi=lp64f --enable-threads=posix --enable-libatomic && \
make -j8

RUN wget https://github.com/Kitware/CMake/releases/download/v3.21.0/cmake-3.21.0.tar.gz && \
echo "4a42d56449a51f4d3809ab4d3b61fd4a96a469e56266e896ce1009b5768bd2ab cmake-3.21.0.tar.gz" | sha256sum -c && \
tar -xzvf cmake-3.21.0.tar.gz && \
cd cmake-3.21.0 && ./bootstrap && make && make install

RUN apt-get update && apt-get install python3-venv -y
RUN python3 -m venv /kruxenv
RUN /kruxenv/bin/pip install astor
RUN /kruxenv/bin/pip install pyserial==3.4

# Provide memset_s for host tools (mpy-cross) that expect it during link time
RUN set -eux; \
cat > /tmp/memset_s.c <<'EOF'
#include <errno.h>
#include <stddef.h>

typedef size_t rsize_t;
typedef int errno_t;

errno_t memset_s(void *dest, rsize_t destsz, int ch, rsize_t count) {
if (!dest) return EINVAL;
if (count > destsz) return EINVAL;
volatile unsigned char *p = (volatile unsigned char *)dest;
while (count--) *p++ = (unsigned char)ch;
__asm__ __volatile__("" : : : "memory"); /* compiler barrier */
return 0;
}
EOF
RUN set -eux; \
gcc -O2 -c /tmp/memset_s.c -o /tmp/memset_s.o; \
ar rcs /usr/local/lib/libmemset_s.a /tmp/memset_s.o; \
nm -g /usr/local/lib/libmemset_s.a | grep -w memset_s

###############
# build-vendor
# everything except COPY ./src
###############
FROM build-base AS build-vendor
ARG DEVICE="maixpy_m5stickv"
ENV DEVICE_BUILTIN="firmware/MaixPy/projects/${DEVICE}/builtin_py"
RUN mkdir /src
WORKDIR /src

COPY ./vendor vendor
RUN find vendor/urtypes -type d -name '__pycache__' -exec rm -rv {} + -depth
RUN find vendor/foundation-ur-py -type d -name '__pycache__' -exec rm -rv {} + -depth
RUN /kruxenv/bin/pip install vendor/embit
RUN rm -rf vendor/embit/src/embit/util/prebuilt && \
rm -rf vendor/embit/src/embit/liquid && \
rm -f vendor/embit/src/embit/psbtview.py && \
rm -f vendor/embit/src/embit/slip39.py && \
rm -f vendor/embit/src/embit/wordlists/slip39.py && \
rm -f vendor/embit/src/embit/util/ctypes_secp256k1.py && \
rm -f vendor/embit/src/embit/util/py_secp256k1.py && \
rm -f vendor/embit/src/embit/util/py_ripemd160.py && \
find vendor/embit -type d -name '__pycache__' -exec rm -rv {} + -depth

COPY ./firmware firmware
RUN find firmware -type d -name '__pycache__' -exec rm -rv {} + -depth
RUN cp -r vendor/urtypes/src/urtypes "${DEVICE_BUILTIN}"
RUN cp -r vendor/foundation-ur-py/src/ur "${DEVICE_BUILTIN}"
RUN cp -r vendor/embit/src/embit "${DEVICE_BUILTIN}"

################
# build-safeclib
################
FROM build-vendor AS build-safeclib
RUN apt-get update -y && apt-get install --no-install-recommends -y \
git make autoconf automake libtool pkg-config ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /src
RUN git clone --depth 1 https://github.com/rurban/safeclib.git
WORKDIR /src/safeclib
RUN ./build-aux/autogen.sh \
&& ./configure --prefix=/opt/safeclib \
&& make -j"$(nproc)" \
&& make install


###########
# build-mpy
###########
FROM build-safeclib AS build-mpy
RUN apt-get update -y && apt-get install --no-install-recommends -y \
git make python3 ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build-safeclib /opt/safeclib /opt/safeclib
ENV LD_LIBRARY_PATH=/opt/safeclib/lib

WORKDIR /src
COPY firmware/MaixPy/components/micropython/core/mpy-cross/ \
firmware/MaixPy/components/micropython/core/mpy-cross/

# Provide memset_s for host tool only (mpy-cross). No upstream repo edits.
RUN set -eux; \
cat > /tmp/memset_s.c <<'EOF'
#include <errno.h>
#include <stddef.h>

typedef size_t rsize_t;
typedef int errno_t;

errno_t memset_s(void *dest, rsize_t destsz, int ch, rsize_t count) {
if (!dest) return EINVAL;
if (count > destsz) return EINVAL;
volatile unsigned char *p = (volatile unsigned char *)dest;
while (count--) *p++ = (unsigned char)ch;
__asm__ __volatile__("" : : : "memory"); /* compiler barrier */
return 0;
}
EOF
RUN set -eux; \
gcc -O2 -c /tmp/memset_s.c -o /tmp/memset_s.o; \
ar rcs /usr/local/lib/libmemset_s.a /tmp/memset_s.o; \
nm -g /usr/local/lib/libmemset_s.a | grep -w memset_s

WORKDIR /src/firmware/MaixPy/components/micropython/core/mpy-cross
RUN set -eux; \
make V=1 LIB="-lm /usr/local/lib/libmemset_s.a"; \
chmod +x ./mpy-cross

##############
# build kapps
# compilation of kapps inside kapps/ folders
#############
FROM build-mpy AS build-kapp
ARG KAPP="nostr"
WORKDIR /work
COPY src/${KAPP}.py /work/${KAPP}.py
RUN mkdir -p /out \
&& /src/firmware/MaixPy/components/micropython/core/mpy-cross/mpy-cross \
-X heapsize=4194304 -O2 -o "/out/${KAPP}.mpy" "/work/${KAPP}.py"
1 change: 1 addition & 0 deletions firmware/MaixPy
Submodule MaixPy added at fa62da
167 changes: 167 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# The MIT License (MIT)

# Copyright (c) 2021-2026 Krux contributors

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

[project]
name = "kapps"
version = "26.03.1"
description = "Krux apps for signing devices"
authors = [{ name = "tadeubas", email = "tadeubas@gmail.com" }]
readme = "README.md"
license = "MIT"
requires-python = ">=3.12.3"

dependencies = [
"embit",
"ur",
"urtypes",
]

[tool.uv.sources]
embit = { git = "https://github.com/diybitcoinhardware/embit.git", branch = "master" }
ur = { git = "https://github.com/selfcustody/foundation-ur-py.git", branch = "master" }
urtypes = { git = "https://github.com/selfcustody/urtypes.git", branch = "main" }

[dependency-groups]
dev = [
"black>=26.3.1,<27",
"pylint>=4.0.5,<5",
"pytest>=9.0.2,<10",
"pytest-cov>=7.0.0,<8",
"pytest-mock>=3.15.1,<4",
"PyQRCode>=1.2.1,<2",
"pycryptodome>=3.23.0,<4",
"vulture>=2.15,<3",
"pre-commit>=4.5.1,<5",
"poethepoet>=0.42.1,<0.43",
"pymarkdown>=0.1.4",
]

[tool.hatch.build.targets.wheel]
packages = ["src"]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.poe.tasks.vulture]
args = [
{ name = "whitelist", help = "make vulture_whitelist.py"},
]
shell = """
set -euo pipefail

vulture src --make-whitelist > vulture_whitelist.py
"""

[tool.poe.tasks.compile]
args = [
{ name = "name", help = "Basename of src/<name>.py", options = [
"-n",
"--name"
], default = "k_qr" },
]
shell = """
set -euo pipefail

image="kapp-${name}"
container="build-kapp-${name}"
docker build --build-arg KAPP=${name} --target build-kapp -t ${image} .
docker rm -f ${container} 2>/dev/null || true
docker create --name ${container} ${image}
docker cp ${container}:/out/${name}.mpy src/${name}.mpy
docker rm -f ${container}
"""

[tool.poe.tasks.compile-all]
shell = """
set -euo pipefail
for f in src/*.py; do
NAME=$(basename $f)
uv run poe compile --name ${NAME}
done
"""

[tool.poe.tasks.get-pubkey]
args = [
{ name = "pubkey", help = "the openssl public key in PEM format", options =[
"-p",
"--pubkey"
], default = "pubkey.pem" }
]
shell = """
set -euo pipefail

PUBKEY=$(openssl ec -noout -text -inform PEM -in ${pubkey} -pubin | tr -d '\n')
PUBKEY=$(echo $PUBKEY | sed 's/Public-Key: (256 bit)pub://g' )
PUBKEY=$(echo $PUBKEY | sed 's/ASN1 OID: secp256k1//g' )
PUBKEY=$(echo $PUBKEY | sed 's/://g' )
PUBKEY=$(echo $PUBKEY | sed 's/ //g' )
echo $PUBKEY
"""

[tool.poe.tasks.kapp]
args = [
{ name = "name", help = "Basename of kapp/<name>.py", options = [
"-n",
"--name",
], default = "nostr" },
{ name = "prvkey", help = "Path to private key PEM", options = [
"-p",
"--prvkey",
], default = "privkey.pem" },
{ name = "pubkey", help = "Path to public key PEM", options = [
"-P",
"--pubkey",
], default = "pubkey.pem" },
]
help = "Compile a .py to .mpy kapp and sign it to .mpy.sig"
shell = """
set -euo pipefail

# prepare signature
PUBKEY=$(uv run poe get-pubkey)

# compile
uv run poe compile --name ${name}

# sign kapp
while true; do
openssl dgst \
-sign ${prvkey} \
-keyform PEM \
-sha256 \
-out src/${name}.mpy.sig \
-binary src/${name}.mpy
if [ "$(wc -c < "src/${name}.mpy.sig")" -eq 70 ]; then
echo "Signature is exactly 70 bytes. Done!"
break
fi
done

# distribute
mkdir -p dist
mv src/${name}.mpy dist/${name}.mpy
mv src/${name}.mpy.sig dist/${name}.mpy.sig
"""

[tool.poe.tasks.git-update]
shell = "git submodule update --init --recursive"
Loading