Repository: raspberrypi/rp2350_hacking_challenge
Branch: main
Commit: 7d369832f4d8
Files: 13
Total size: 16.0 KB
Directory structure:
gitextract_9iz5rd3f/
├── .gitignore
├── CMakeLists.txt
├── README.md
├── ec_private_key.pem
├── ec_public_key.pem
├── enable_secureboot.sh
├── keygen.sh
├── lock_chip.sh
├── main.c
├── otp.json
├── pico_sdk_import.cmake
├── read_otp_secret.sh
└── write_otp_secret.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
build
================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.13)
# initialize the SDK based on PICO_SDK_PATH
# note: this must happen before project()
include(pico_sdk_import.cmake)
project(rp2350_hacking_challenge_debug_version)
pico_sdk_init()
# First executable
add_executable(rp2350_hacking_challenge_debug_version)
target_sources(rp2350_hacking_challenge_debug_version PRIVATE
main.c)
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../lib")
target_link_libraries(rp2350_hacking_challenge_debug_version PRIVATE
pico_stdlib
hardware_pio
hardware_i2c
hardware_powman
)
# enable usb output, disable uart output
pico_enable_stdio_usb(rp2350_hacking_challenge_debug_version 1)
pico_enable_stdio_uart(rp2350_hacking_challenge_debug_version 0)
# Signing and hashing
pico_set_binary_type(rp2350_hacking_challenge_debug_version no_flash)
pico_sign_binary(rp2350_hacking_challenge_debug_version ${CMAKE_CURRENT_SOURCE_DIR}/ec_private_key.pem)
pico_hash_binary(rp2350_hacking_challenge_debug_version)
pico_package_uf2_output(rp2350_hacking_challenge_debug_version 0x10000000)
pico_set_otp_key_output_file(rp2350_hacking_challenge_debug_version ${CMAKE_CURRENT_LIST_DIR}/otp.json)
pico_add_extra_outputs(rp2350_hacking_challenge_debug_version)
# Second executable with GLITCH_DETECTOR_ON defined
add_executable(rp2350_hacking_challenge_secure_version)
target_sources(rp2350_hacking_challenge_secure_version PRIVATE
main.c)
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../lib")
target_compile_definitions(rp2350_hacking_challenge_secure_version PRIVATE SECURE_VERSION)
target_link_libraries(rp2350_hacking_challenge_secure_version PRIVATE
pico_stdlib
hardware_pio
hardware_i2c
)
# disable usb output, disable uart output
pico_enable_stdio_usb(rp2350_hacking_challenge_secure_version 0)
pico_enable_stdio_uart(rp2350_hacking_challenge_secure_version 0)
# Signing and hashing
pico_set_binary_type(rp2350_hacking_challenge_secure_version no_flash)
pico_sign_binary(rp2350_hacking_challenge_secure_version ${CMAKE_CURRENT_SOURCE_DIR}/ec_private_key.pem)
pico_hash_binary(rp2350_hacking_challenge_secure_version)
pico_package_uf2_output(rp2350_hacking_challenge_secure_version 0x10000000)
pico_set_otp_key_output_file(rp2350_hacking_challenge_secure_version ${CMAKE_CURRENT_LIST_DIR}/otp.json)
pico_add_extra_outputs(rp2350_hacking_challenge_secure_version)
================================================
FILE: README.md
================================================
# RP2350 Hacking Challenge
Welcome to the Raspberry Pi RP2350 hacking challenge and bug bounty!
Watch our quick explainer video:
[](https://hextree.io/rp2350-hacking-challenge)
## Update Jan 14th 2025
**Congatulations to the 4 winners! Read about them all [here](https://www.raspberrypi.com/news/security-through-transparency-rp2350-hacking-challenge-results-are-in/).**
A huge thank you to Thomas Roth and the team at Hextree.io for helping us develop and launch this challenge back in 2024!
## Update Jan 1st 2025
**The RP2350 Hacking Challenge has concluded.**
There have been some fantastic submissions! We'll announce winners and publish details on Jan 14th 2025.
## Update Sept 5th 2024
No breaks have been reported yet.
We are doubling the prize to $20,000!
We've extended the term of the challenge, it now runs until midnight on December 31st 2024 (UK time)
The goal is easy: Find an attack that lets you dump a secret hidden in OTP ROW 0xc08 - the secret is 128-bit long, and protected by `OTP_DATA_PAGE48_LOCK1` and RP2350's secure boot!
If you think you have found a break email us at [doh@raspberrypi.com](mailto:doh@raspberrypi.com) with details - we will ship you a Pico2 with a custom secret hidden in it. If you manage to extract it, you win the $20,000!
Good luck!
## Disclaimer
For this challenge we will do the following persistent & irreversible changes to your RP2350:
- Writing bootkey0 (with a public key - or you can generate your own & build your own firmware)
- Enabling secure-boot via `crit1.secure_boot_enable` (but with public keys)
- Disable debug via `crit1.debug_disable`
- Overwrite & lock data in OTP ROW 0xc08
- *Enabling security will permanently disable both Hazard3 RISC-V cores (M33 cores will still be operable)*
## Setup - Pico 2 board
- Connect an RP2350 in BOOTSEL mode to your computer via USB
- The repository already contains signing keys: `ec_private_key.pem` and `ec_public_key.pem`. If you want to generate your own keys you can run `keygen.sh` to generate new ones using openSSL.
- Next we write the secret that we want to hide using: `./write_otp_secret.sh` - this is irreversible, as we can't "erase" OTP.
- You can check whether this write was successful by running `./read_otp_secret.sh`
- Next we build our project:
- `mkdir build`
- `cd build`
- `cmake -DPICO_PLATFORM=rp2350 -DPICO_BOARD=pico2 ..`
- `make`
- `cd ..`
- Next we enable secure-boot on the chip by running `enable_secureboot.sh` (This irreversibly enables secure-boot! Make sure you keep a backup of your keys!)
- To fully lock down the chip including disabling debugging and enabling the glitch detectors, please run `lock_chip.sh`
- And now we are ready to install the firmware:
- Either copy `rp2350_hacking_challenge_debug_version.uf2` or `rp2350_hacking_challenge_secure_version.uf2`
## What's the difference between the debug and the secure version?
The debug version shows how to read the OTP secret that you need to extract, and also gives
some debug output on what's going on in the chip.
As the printfs etc. might be susceptible to fault-injection attacks we have disabled them in
the secure version.
Our "golden" challenge Pico 2 will run the secure-version of the firmware, with the binary copied to RAM.
## Using the chip in the future
By participating in this challenge you are permanently enabling secure-boot on your device.
Any firmware you want to install in the firmware you need to sign yourself. You can enable
signing for other projects by simply adding this to the CMakeLists.txt (this needs to be above the `pico_add_extra_outputs`) and copying the
`ec_private_key.pem` to your source directoy.
```
# Signing and hashing
pico_sign_binary(project_name ${CMAKE_CURRENT_SOURCE_DIR}/ec_private_key.pem)
pico_hash_binary(project_name)
pico_set_otp_key_output_file(project_name ${CMAKE_CURRENT_LIST_DIR}/otp.json)
```
## Rules, Terms and Conditions
Please see [here](https://www.raspberrypi.com/def-con-2024-challenge/) for terms, conditions and rules for this challenge.
================================================
FILE: ec_private_key.pem
================================================
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIMIQpHEvQcq/Eu/VTQcHfVXY7jOBaAGDnFEH0v9oF6gsoAcGBSuBBAAK
oUQDQgAEV3ujCA02hzwSiLK8U5QRaVL+UvTFBdtsJNpv7o8Ssts8WL2hBjAeFcNY
gOge/5aK+WZLzhv6rWWWkA++zlIL0Q==
-----END EC PRIVATE KEY-----
================================================
FILE: ec_public_key.pem
================================================
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEV3ujCA02hzwSiLK8U5QRaVL+UvTFBdts
JNpv7o8Ssts8WL2hBjAeFcNYgOge/5aK+WZLzhv6rWWWkA++zlIL0Q==
-----END PUBLIC KEY-----
================================================
FILE: enable_secureboot.sh
================================================
#!/bin/bash
echo "Are you sure you want to enable secure-boot?"
echo "Any future firmware will have to be signed by the keys"
echo "in this repo (or the ones you generated)."
echo ""
echo "Please type ENABLE to continue"
read -r user_input
if [ "$user_input" != "ENABLE" ]; then
echo "Operation canceled."
exit 1
fi
echo "Loading otp.json onto Pico2!"
picotool otp load otp.json
================================================
FILE: keygen.sh
================================================
#!/bin/bash
set -e
set -v
PRIVATE_KEY=ec_private_key.pem
PUBLIC_KEY=ec_public_key.pem
[ -e $PRIVATE_KEY ] || [ -e $PUBLIC_KEY ] && { echo "Keys already exist. Not overwriting."; exit 1; }
openssl ecparam -genkey -name secp256k1 -noout -out $PRIVATE_KEY
openssl ec -in $PRIVATE_KEY -pubout -out $PUBLIC_KEY
================================================
FILE: lock_chip.sh
================================================
#!/bin/bash
echo "Are you sure you want to fully lock down the chip?"
echo "Please type PLEASE LOCK to continue"
read -r user_input
if [ "$user_input" != "PLEASE LOCK" ]; then
echo "Operation canceled."
exit 1
fi
# Disable debugging features
picotool otp set OTP_DATA_CRIT1.DEBUG_DISABLE 1
# Disable other boot keys
picotool otp set OTP_DATA_BOOT_FLAGS1.KEY_INVALID 0xe
# Enable glitch detector
picotool otp set OTP_DATA_CRIT1.GLITCH_DETECTOR_ENABLE 1
# Highest sensitivity
picotool otp set OTP_DATA_CRIT1.GLITCH_DETECTOR_SENS 3
# Lock writes to PAGE1, 2, 48
picotool otp set --raw OTP_DATA_PAGE1_LOCK1 0x101010
picotool otp set --raw OTP_DATA_PAGE2_LOCK1 0x101010
picotool otp set --raw OTP_DATA_PAGE48_LOCK1 0x101010
set +e
set +v
echo -e "\n\n"
echo "Your chip is locked! Good luck!"
================================================
FILE: main.c
================================================
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/watchdog.h"
#include "pico/bootrom.h"
// #include "hardware/regs/glitch_detector.h"
#include "hardware/regs/powman.h"
#include "hardware/regs/otp_data.h"
#include "hardware/structs/powman.h"
#include "hardware/structs/otp.h"
// In the secure version we don't use dprintf, as an FI
// attack on dprintf could potentially be used to leak the OTP secret.
#ifdef SECURE_VERSION
#define dprintf(fmt, ...) ((void)0)
#else
#define dprintf printf
#endif
static inline bool is_locked() {
volatile uint32_t * otp_page48_lock1_ptr = ((uint32_t *)(OTP_DATA_BASE + (0xfe0*2)));
if(otp_page48_lock1_ptr[0] != 0x3C3C0000) {
return false;
}
if(otp_page48_lock1_ptr[1] != 0x3C) {
return false;
}
return true;
}
static inline void lock_otp_secret() {
dprintf("Locking OTP secret...\n");
otp_cmd_t cmd;
cmd.flags = OTP_DATA_PAGE48_LOCK1_ROW | OTP_CMD_ECC_BITS | OTP_CMD_WRITE_BITS;
// 3 redundant copies
uint32_t value = 0x3c3c3c;
uint32_t ret = rom_func_otp_access(&value, sizeof(value), cmd);
if (ret) {
dprintf("\tLocking failed with error: %d\n", ret);
while(1) {
}
} else {
dprintf("\tLocking succeeded!ECC Write succeeded!\n");
}
}
static inline void lock_sw_lock_48() {
dprintf("Locking OTP secret via SW_LOCK\n");
// Lock the OTP memory so it's not readable afterwards
// - [3:2] - Non-Secure lock status
// - [1:0] - Secure lock status
// - 0 = read_write 1 = read_only 3 = inaccessible
otp_hw->sw_lock[48] = 0b1111;
dprintf("\tDone!\n\n");
}
int main()
{
#ifndef SECURE_VERSION
stdio_init_all();
// Wait 5 seconds to give the user a chance to connect to the USB serial console
sleep_ms(5000);
dprintf("Welcome to the Raspberry Pi RP2350 Hacking Challenge!\n\n");
dprintf("The goal is easy: Find an attack that lets you dump a secret\n");
dprintf("hidden in OTP ROW 0xc08 - the secret is 64-bit long, and\n");
dprintf("protected by OTP_DATA_PAGE48_LOCK1 and RP2350's secure boot!\n");
dprintf("\n");
dprintf("\n");
dprintf("Good luck!\n\n");
#endif
// Before we do anything else we check whether our secret OTP pages are locked.
// If they aren't locked yet we write OTP_DATA_PAGE48_LOCK1 to 0x3c3c3c.
// This is persistent and prevents reading of the OTP pages via picotool and
// from non-secure code. Secure code (i.e. if you manage to bypass secure-boot)
// will still be able to access the secret. Good luck!
if(is_locked()) {
dprintf("OTP area is locked!\n");
} else {
dprintf("OTP area is not locked!\n");
lock_otp_secret();
}
puts("");
#ifndef SECURE_VERSION
// This is how you could leak the first 4 bytes of the secret:
dprintf("Test access to the OTP before it's locked using SW_LOCK:\n");
volatile uint32_t * otp_guarded_data_ptr = ((uint32_t *)(OTP_DATA_GUARDED_BASE + (0xc08*2)));
dprintf("%04X", *otp_guarded_data_ptr & 0xFFFF);
dprintf("%04X\n\n", (*otp_guarded_data_ptr & 0xFFFF0000) >> 16);
#endif
// Next, we lock the OTP area down even further using SW_LOCK48 - this ensures that
// the secret can't be retrieved if you exploit the application/gain code-exec after
// this point!
lock_sw_lock_48();
#ifndef SECURE_VERSION
dprintf("Test access to the OTP after it's locked using SW_LOCK:\n");
// We are using an unguarded (non-ecc) read here, as otherwise we cause a bus fault.
// (See "OTP Address Map" section in the datasheet.)
volatile uint32_t * otp_data_ptr = ((uint32_t *)(OTP_DATA_BASE + (0xc08*2)));
dprintf("%04X", *otp_data_ptr & 0xFFFF);
dprintf("%04X\n", (*otp_data_ptr & 0xFFFF0000) >> 16);
#endif
while(1) {
}
}
================================================
FILE: otp.json
================================================
{
"boot_flags1": {
"key_valid": 1
},
"bootkey0": [
58,
3,
118,
188,
215,
78,
85,
178,
188,
139,
8,
225,
26,
108,
233,
192,
240,
124,
108,
141,
39,
41,
112,
61,
217,
67,
178,
96,
31,
92,
216,
65
],
"crit1": {
"secure_boot_enable": 1
}
}
================================================
FILE: pico_sdk_import.cmake
================================================
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
GIT_SUBMODULES_RECURSE FALSE
)
else ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
)
endif ()
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
FetchContent_Populate(pico_sdk)
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})
================================================
FILE: read_otp_secret.sh
================================================
#!/bin/bash
picotool otp get -e 0xc08 | grep VALUE
picotool otp get -e 0xc09 | grep VALUE
picotool otp get -e 0xc0a | grep VALUE
picotool otp get -e 0xc0b | grep VALUE
picotool otp get -e 0xc0c | grep VALUE
picotool otp get -e 0xc0d | grep VALUE
picotool otp get -e 0xc0e | grep VALUE
picotool otp get -e 0xc0f | grep VALUE
================================================
FILE: write_otp_secret.sh
================================================
#!/bin/bash
set -e
set -v
picotool otp set -e 0xc08 0xc0ff
picotool otp set -e 0xc09 0xffee
picotool otp set -e 0xc0a 0xc0ff
picotool otp set -e 0xc0b 0xffee
picotool otp set -e 0xc0c 0xc0ff
picotool otp set -e 0xc0d 0xffee
picotool otp set -e 0xc0e 0xc0ff
picotool otp set -e 0xc0f 0xffee
gitextract_9iz5rd3f/ ├── .gitignore ├── CMakeLists.txt ├── README.md ├── ec_private_key.pem ├── ec_public_key.pem ├── enable_secureboot.sh ├── keygen.sh ├── lock_chip.sh ├── main.c ├── otp.json ├── pico_sdk_import.cmake ├── read_otp_secret.sh └── write_otp_secret.sh
SYMBOL INDEX (4 symbols across 1 files)
FILE: main.c
function is_locked (line 21) | static inline bool is_locked() {
function lock_otp_secret (line 32) | static inline void lock_otp_secret() {
function lock_sw_lock_48 (line 49) | static inline void lock_sw_lock_48() {
function main (line 59) | int main()
Condensed preview — 13 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (18K chars).
[
{
"path": ".gitignore",
"chars": 6,
"preview": "build\n"
},
{
"path": "CMakeLists.txt",
"chars": 2428,
"preview": "cmake_minimum_required(VERSION 3.13)\n# initialize the SDK based on PICO_SDK_PATH\n# note: this must happen before project"
},
{
"path": "README.md",
"chars": 4117,
"preview": "# RP2350 Hacking Challenge\n\nWelcome to the Raspberry Pi RP2350 hacking challenge and bug bounty!\n\nWatch our quick explai"
},
{
"path": "ec_private_key.pem",
"chars": 223,
"preview": "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIMIQpHEvQcq/Eu/VTQcHfVXY7jOBaAGDnFEH0v9oF6gsoAcGBSuBBAAK\noUQDQgAEV3ujCA02hzwSiLK8"
},
{
"path": "ec_public_key.pem",
"chars": 174,
"preview": "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEV3ujCA02hzwSiLK8U5QRaVL+UvTFBdts\nJNpv7o8Ssts8WL2hBjAeFcNYgOge"
},
{
"path": "enable_secureboot.sh",
"chars": 389,
"preview": "#!/bin/bash\n\necho \"Are you sure you want to enable secure-boot?\"\necho \"Any future firmware will have to be signed by the"
},
{
"path": "keygen.sh",
"chars": 310,
"preview": "#!/bin/bash\nset -e\nset -v\n\nPRIVATE_KEY=ec_private_key.pem\nPUBLIC_KEY=ec_public_key.pem\n\n[ -e $PRIVATE_KEY ] || [ -e $PUB"
},
{
"path": "lock_chip.sh",
"chars": 801,
"preview": "#!/bin/bash\n\necho \"Are you sure you want to fully lock down the chip?\"\necho \"Please type PLEASE LOCK to continue\"\nread -"
},
{
"path": "main.c",
"chars": 3625,
"preview": "#include <stdio.h>\n#include \"pico/stdlib.h\"\n#include \"hardware/pio.h\"\n#include \"hardware/watchdog.h\"\n#include \"pico/boot"
},
{
"path": "otp.json",
"chars": 532,
"preview": "{\n \"boot_flags1\": {\n \"key_valid\": 1\n },\n \"bootkey0\": [\n 58,\n 3,\n 118,\n 188,\n"
},
{
"path": "pico_sdk_import.cmake",
"chars": 3164,
"preview": "# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake\n\n# This can be dropped into an external project to he"
},
{
"path": "read_otp_secret.sh",
"chars": 324,
"preview": "#!/bin/bash\npicotool otp get -e 0xc08 | grep VALUE\npicotool otp get -e 0xc09 | grep VALUE\npicotool otp get -e 0xc0a | gr"
},
{
"path": "write_otp_secret.sh",
"chars": 290,
"preview": "#!/bin/bash\nset -e\nset -v\npicotool otp set -e 0xc08 0xc0ff\npicotool otp set -e 0xc09 0xffee\npicotool otp set -e 0xc0a 0x"
}
]
About this extraction
This page contains the full source code of the raspberrypi/rp2350_hacking_challenge GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 13 files (16.0 KB), approximately 4.9k tokens, and a symbol index with 4 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.