Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
bbcfb56
Validate Bitcoin Core state types
BenWestgate Sep 21, 2026
7ff08a6
Reject non-ASCII normalized input
BenWestgate Sep 21, 2026
4f01561
generation: Fold ASCII case per character
BenWestgate Sep 23, 2026
9f42ece
bip93: Enforce the BIP173 HRP limit
BenWestgate Sep 23, 2026
9a05bb3
bip93: Drop oversized frozen vector
BenWestgate Sep 23, 2026
e397fcc
bip93: use generalized-HRP draft vectors
BenWestgate Sep 24, 2026
b1b1b27
tests: Correct HRP vector provenance
BenWestgate Sep 27, 2026
1201944
docs: Record security audit verdict
BenWestgate Sep 23, 2026
654039b
docs: Link audited GUI revisions
BenWestgate Sep 23, 2026
31598f0
docs: Separate recovery accident and tampering checks
BenWestgate Sep 25, 2026
d07024a
docs: Clarify no-record restore authorization
BenWestgate Sep 29, 2026
5d17ed7
docs: Define trusted computer boundary
BenWestgate Sep 25, 2026
9730835
docs: keep CLI safety footer within 80 columns
BenWestgate Sep 27, 2026
ede98a8
test: match shortened CLI safety footer
BenWestgate Sep 27, 2026
02bfc6a
Gate release on exact artifacts
BenWestgate Sep 24, 2026
9cf9f44
release: Gate publication on qualified artifacts
BenWestgate Sep 27, 2026
d6679cc
Expose vector construction helpers
BenWestgate Sep 24, 2026
cde312b
api: Reject Unicode lookalikes in u5 conversion
codex Sep 28, 2026
00aae9b
docs: Record adversarial finding dispositions
BenWestgate Oct 4, 2026
48e2fc0
docs: Move audit ledger under security docs
BenWestgate Oct 5, 2026
5ab20cd
docs: Remove superseded audit ledger path
BenWestgate Oct 5, 2026
fce2705
docs: Refresh adversarial audit status
BenWestgate Oct 5, 2026
72dbd69
docs: Record post-audit release gates
BenWestgate Oct 5, 2026
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
244 changes: 230 additions & 14 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,57 @@
name: Publish release

on:
release:
types: [published]
push:
tags:
- "v*"

env:
SOURCE_DATE_EPOCH: "1763060600"
BITCOIN_CORE_ARCHIVE: bitcoin-32.0rc2-x86_64-linux-gnu.tar.gz
BITCOIN_CORE_SHA256: 0255103718033e6aee15fa944717fc277e047b845bff1e7408af0ea732d8d0c1
BITCOIN_CORE_URL: https://bitcoincore.org/bin/bitcoin-core-32.0/test.rc2

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
contents: read
outputs:
python_versions: ${{ steps.metadata.outputs.python_versions }}
version: ${{ steps.metadata.outputs.version }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.13"
- run: >-
python -c "import os,tomllib;
version=tomllib.load(open('pyproject.toml','rb'))['project']['version'];
assert os.environ['GITHUB_REF_NAME'] == f'v{version}'"
- id: metadata
name: Check release metadata
run: |
python - <<'PY'
import json
import os
import tomllib

with open("pyproject.toml", "rb") as file:
project = tomllib.load(file)["project"]
version = project["version"]
if os.environ["GITHUB_REF_NAME"] != f"v{version}":
raise SystemExit("release tag does not match package version")
prefix = "Programming Language :: Python :: "
versions = sorted(
classifier.removeprefix(prefix)
for classifier in project["classifiers"]
if classifier.startswith(prefix) and classifier.removeprefix(prefix).count(".") == 1
)
if not versions:
raise SystemExit("no supported Python versions are classified")
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
output.write(f"version={version}\n")
output.write(f"python_versions={json.dumps(versions, separators=(',', ':'))}\n")
PY
- run: >-
python -m pip install --require-hashes
-r requirements/release-build-dependencies.txt
Expand All @@ -33,28 +64,213 @@ jobs:
tar --sort=name --mtime="@${SOURCE_DATE_EPOCH}" --owner=0 --group=0 --numeric-owner \
-C normalized-sdist -cf - "codex32-${GITHUB_REF_NAME#v}" | gzip -n > "$archive.new"
mv "$archive.new" "$archive"
- name: Attach distributions to the GitHub release
run: gh release upload "$GITHUB_REF_NAME" dist/*
env:
GH_TOKEN: ${{ github.token }}
- name: Record built artifact provenance
run: |
mkdir provenance
git rev-parse HEAD > provenance/source-commit.txt
sha256sum dist/* > provenance/artifact-sha256.txt
python -m pip freeze --all > provenance/build-environment.txt
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: distributions
path: dist/
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: build-provenance
path: provenance/

publish:
supported-matrix:
needs: build
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ${{ fromJSON(needs.build.outputs.python_versions) }}
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- run: python -m pip install --upgrade pip
- run: python -m pip install --require-hashes -r requirements/cli-build-dependencies.txt
- name: Install legacy wallet-test oracle when present
shell: bash
run: |
if [[ -f requirements/test-wallet-dependencies.txt ]]; then
python -m pip install --no-build-isolation --require-hashes \
-r requirements/test-wallet-dependencies.txt
fi
- run: python -m pip install --no-build-isolation -e '.[dev]'
- run: python -m pip check
- run: python -m pytest -q
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: distributions
path: dist/
- name: Install and test the exact wheel and sdist
shell: bash
run: |
for artifact in dist/codex32-*.whl dist/codex32-*.tar.gz; do
python tools/verify_wheel_environment.py --artifact "$artifact"
done
- name: Record qualification dependencies
shell: bash
run: >-
python -m pip freeze --all >
"qualification-${RUNNER_OS}-${{ matrix.python-version }}.txt"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: qualification-${{ runner.os }}-${{ matrix.python-version }}
path: qualification-*.txt

quality:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.13"
- run: python -m pip install --upgrade pip
- run: python -m pip install --require-hashes -r requirements/cli-build-dependencies.txt
- name: Install legacy wallet-test oracle when present
run: |
if [[ -f requirements/test-wallet-dependencies.txt ]]; then
python -m pip install --no-build-isolation --require-hashes \
-r requirements/test-wallet-dependencies.txt
fi
- run: python -m pip install --no-build-isolation -e '.[dev]'
- run: python -O -m pytest -q
- run: python -m mypy src/codex32
- run: python -m ruff check .
- run: python -m ruff format --check .
- run: python tools/differential_correction.py --verify
- name: Run legacy wallet differential when present
run: |
if [[ -f tools/differential_wallet.py ]]; then
python tools/differential_wallet.py --verify
fi

bitcoin-core:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.13"
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: distributions
path: dist/
- name: Install qualified wheel
run: python -m pip install --no-deps dist/codex32-*.whl
- name: Download pinned Bitcoin Core
run: |
curl --fail --location --proto '=https' --tlsv1.2 \
--output "$RUNNER_TEMP/$BITCOIN_CORE_ARCHIVE" \
"$BITCOIN_CORE_URL/$BITCOIN_CORE_ARCHIVE"
echo "$BITCOIN_CORE_SHA256 $RUNNER_TEMP/$BITCOIN_CORE_ARCHIVE" | sha256sum --check
tar -xzf "$RUNNER_TEMP/$BITCOIN_CORE_ARCHIVE" -C "$RUNNER_TEMP"
- name: Run real-Core release integration
env:
PYTHONNOUSERSITE: "1"
run: |
core="$RUNNER_TEMP/bitcoin-32.0rc2/bin"
"$core/bitcoind" --version | head -1 | tee bitcoin-core-version.txt
python tools/bitcoin_core_regtest.py \
--bitcoind "$core/bitcoind" \
--bitcoin-cli "$core/bitcoin-cli"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: bitcoin-core-provenance
path: bitcoin-core-version.txt

publish:
needs: [build, supported-matrix, quality, bitcoin-core]
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/codex32
permissions:
contents: read
contents: write
id-token: write
steps:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: distributions
path: dist/
- name: Publish distributions to PyPI
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: build-provenance
path: provenance/
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: bitcoin-core-provenance
path: provenance/
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: qualification-*
path: provenance/dependencies/
merge-multiple: true
- name: Verify qualified artifact bytes
run: sha256sum --check provenance/artifact-sha256.txt
- name: Record release qualification
env:
RELEASE_VERSION: ${{ needs.build.outputs.version }}
MATRIX_RESULT: ${{ needs.supported-matrix.result }}
QUALITY_RESULT: ${{ needs.quality.result }}
CORE_RESULT: ${{ needs.bitcoin-core.result }}
run: |
python - <<'PY'
import json
import os
from pathlib import Path

hashes = {}
for line in Path("provenance/artifact-sha256.txt").read_text().splitlines():
digest, path = line.split(maxsplit=1)
hashes[Path(path).name] = digest
record = {
"source_commit": Path("provenance/source-commit.txt").read_text().strip(),
"tag": os.environ["GITHUB_REF_NAME"],
"version": os.environ["RELEASE_VERSION"],
"bitcoin_core": Path("provenance/bitcoin-core-version.txt").read_text().strip(),
"artifact_sha256": hashes,
"qualification": {
"supported_matrix": os.environ["MATRIX_RESULT"],
"quality": os.environ["QUALITY_RESULT"],
"bitcoin_core": os.environ["CORE_RESULT"],
},
}
Path("provenance/release-qualification.json").write_text(
json.dumps(record, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
PY
- name: Bundle release provenance
run: >-
tar -czf "codex32-${{ needs.build.outputs.version }}-provenance.tar.gz"
-C provenance .
- name: Stage draft GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: |
artifact="codex32-${{ needs.build.outputs.version }}-provenance.tar.gz"
if draft="$(gh release view "$GITHUB_REF_NAME" --json isDraft --jq .isDraft 2>/dev/null)"; then
[ "$draft" = true ] || { echo "release is already public" >&2; exit 1; }
gh release upload "$GITHUB_REF_NAME" dist/* "$artifact" --clobber
else
gh release create "$GITHUB_REF_NAME" --draft --verify-tag \
--title "$GITHUB_REF_NAME" --generate-notes dist/* "$artifact"
fi
- name: Publish qualified distributions to PyPI
uses: pypa/gh-action-pypi-publish@a892a5a61159132606e93a2fa6f4358831b04d26 # v1.14.2
with:
packages-dir: dist/
- name: Publish GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: gh release edit "$GITHUB_REF_NAME" --draft=false
26 changes: 19 additions & 7 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
# Security

codex32 handles wallet recovery material. Use a trusted computer and have the
codex32 handles wallet recovery material. A **trusted computer** is under your
exclusive control, is not known or suspected to be compromised, and runs an
operating system and other software you trust for the operation. For wallet
work, that trusted software includes Python, codex32, the terminal,
`bitcoin-cli`, Bitcoin Core, and their relevant configuration. Wallet encryption,
application permissions, and RPC authentication do not make a compromised
computer trusted. Have the
software and recovery plan reviewed before relying on it with funds. For
stronger isolation, use codex32 and the Bitcoin Core signing wallet only on a
computer kept permanently offline.
stronger isolation, keep the trusted computer disconnected from every network
before, while, and after it handles private recovery or signing material.

A valid checksum detects many copying mistakes. It does not prove that a backup
belongs to your wallet. A correction is only a suggestion; compare it with the
physical backup and wallet information kept elsewhere.

Python, your terminal, and your operating system may retain secret text in
memory or scrollback. codex32 does not intentionally save secrets and keeps
them out of command arguments and normal machine output, but it cannot
guarantee that every copy is erased from swap, hibernation data, or crash
dumps. Using Tails and shutting down when finished helps mitigate this Python
limitation.
them out of command arguments, logs, and unrelated output. Commands that create,
recover, derive, correct, or explicitly export recovery material intentionally
display it when that is their purpose. codex32 cannot guarantee that every copy
is erased from swap, hibernation data, or crash dumps. Using Tails and shutting
down when finished helps mitigate this Python limitation.

Run a command first and enter recovery material at its prompt, or redirect its
standard input from a separately protected source. Do not embed recovery text in
shell command text: shell history, terminal logging, wrappers, and process
tooling may retain it even though codex32 never receives it as an argument.

## Report a security problem

Expand Down
Loading
Loading