@freedomofpress/cometbft 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,81 @@
1
+ name: Auto Bump Dependencies
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "0 4 * * *"
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ auto-bump:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write
13
+ pull-requests: write
14
+
15
+ steps:
16
+ - name: Checkout
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Setup Node.js
20
+ uses: actions/setup-node@v4
21
+ with:
22
+ node-version: 20
23
+
24
+ - name: Install npm-check-updates
25
+ run: npm install -g npm-check-updates
26
+
27
+ - name: Bump dependencies
28
+ run: |
29
+ ncu -u --peer
30
+ # ncu -u --reject '/^typescript$/'
31
+
32
+ - name: Setup Buf
33
+ uses: bufbuild/buf-setup-action@v1
34
+ with:
35
+ github_token: ${{ secrets.GITHUB_TOKEN }}
36
+ # buf_token: ${{ secrets.BUF_TOKEN }} # uncomment if you need auth to buf.build
37
+
38
+ - name: Cache Buf modules
39
+ uses: actions/cache@v4
40
+ with:
41
+ path: ~/.cache/buf
42
+ key: ${{ runner.os }}-buf-${{ hashFiles('buf.yaml', 'buf.gen.yaml', 'buf.lock') }}
43
+ restore-keys: |
44
+ ${{ runner.os }}-buf-
45
+
46
+ - name: Generate protobufs
47
+ run: npm run proto:gen
48
+
49
+ - name: Install updated deps
50
+ run: npm install
51
+
52
+ - name: Run npm audit fix
53
+ run: npm audit fix || true
54
+
55
+ - name: Commit changes (bump + audit fix)
56
+ run: |
57
+ git config user.name "github-actions[bot]"
58
+ git config user.email "github-actions[bot]@users.noreply.github.com"
59
+ git add package.json package-lock.json
60
+ git diff --cached --quiet || git commit -m "chore: bump dependencies and apply audit fix"
61
+ continue-on-error: true
62
+
63
+ - name: Run tests on updated deps
64
+ id: test
65
+ run: npm test
66
+ continue-on-error: true
67
+
68
+ - name: If tests pass, push to main
69
+ if: steps.test.outcome == 'success'
70
+ run: git push origin HEAD:main
71
+
72
+ - name: If tests fail, create PR
73
+ if: steps.test.outcome == 'failure'
74
+ uses: peter-evans/create-pull-request@v5
75
+ with:
76
+ branch: bump-deps-failed
77
+ title: "chore: bump dependencies and audit fix (tests failed)"
78
+ body: |
79
+ Dependencies were updated and `npm audit fix` was run, but tests failed.
80
+ Please investigate and merge manually.
81
+ commit-message: "chore: bump dependencies and audit fix (tests failed)"
@@ -0,0 +1,60 @@
1
+ name: Publish Package
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ jobs:
9
+ run-tests:
10
+ uses: ./.github/workflows/test.yml
11
+
12
+ publish:
13
+ needs: run-tests
14
+ runs-on: ubuntu-latest
15
+
16
+ if: startsWith(github.ref, 'refs/tags/v')
17
+
18
+ permissions:
19
+ contents: read
20
+ packages: write
21
+ id-token: write
22
+
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+
26
+ - uses: actions/setup-node@v4
27
+ with:
28
+ node-version: 20
29
+ registry-url: "https://registry.npmjs.org"
30
+
31
+ - name: Update npm
32
+ run: npm install -g npm@latest
33
+
34
+ - name: Install deps
35
+ run: npm ci
36
+
37
+ - name: Setup Buf
38
+ uses: bufbuild/buf-setup-action@v1
39
+ with:
40
+ github_token: ${{ secrets.GITHUB_TOKEN }}
41
+
42
+ - name: Cache Buf modules
43
+ uses: actions/cache@v4
44
+ with:
45
+ path: ~/.cache/buf
46
+ key: ${{ runner.os }}-buf-${{ hashFiles('buf.yaml', 'buf.gen.yaml', 'buf.lock') }}
47
+ restore-keys: |
48
+ ${{ runner.os }}-buf-
49
+
50
+ - name: Generate protobufs
51
+ run: npm run proto:gen
52
+
53
+ - name: Build
54
+ run: npm run build
55
+
56
+ - name: Dry-run publish
57
+ run: npm publish --dry-run
58
+
59
+ - name: Publish to npm
60
+ run: npm publish --access public
@@ -0,0 +1,56 @@
1
+ name: Lint, Test, Proto Gen and Coverage
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+ workflow_call:
10
+
11
+ jobs:
12
+ lint-test:
13
+ runs-on: ubuntu-latest
14
+
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Setup Node.js
20
+ uses: actions/setup-node@v4
21
+ with:
22
+ node-version: 20
23
+ cache: npm
24
+
25
+ - name: Install dependencies
26
+ run: npm ci
27
+
28
+ - name: Run ESLint
29
+ run: npm run lint
30
+
31
+ - name: Setup Buf
32
+ uses: bufbuild/buf-setup-action@v1
33
+ with:
34
+ github_token: ${{ secrets.GITHUB_TOKEN }}
35
+
36
+ - name: Cache Buf modules
37
+ uses: actions/cache@v4
38
+ with:
39
+ path: ~/.cache/buf
40
+ key: ${{ runner.os }}-buf-${{ hashFiles('buf.yaml', 'buf.gen.yaml', 'buf.lock') }}
41
+ restore-keys: |
42
+ ${{ runner.os }}-buf-
43
+
44
+ - name: Generate protobufs
45
+ run: npm run proto:gen
46
+
47
+ - name: Ensure generated code is up to date
48
+ run: |
49
+ if ! git diff --quiet; then
50
+ echo "Generated protobufs are not up-to-date. Run 'npm run proto:gen' and commit the changes."
51
+ git --no-pager diff
52
+ exit 1
53
+ fi
54
+
55
+ - name: Run tests
56
+ run: npm run test
package/Readme.md ADDED
@@ -0,0 +1,54 @@
1
+ # cometbft-ts
2
+
3
+ _Note: this library has not been audited, thus its security has not been independently verified._
4
+
5
+ `cometbft-ts` is a small TypeScript library for verifying CometBFT commits in the browser. It takes the JSON you get from CometBFT ABCI/RPC for a `commit` and its `validators`, constructs canonical sign-bytes via protobuf, and verifies Ed25519 signatures and >2/3 quorum. It is verification-only and throws on any cryptographic or format error. It uses the native [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API). The library is developed as part of [WEBCAT](https://github.com/freedomofpress/webcat) and is **not audited**.
6
+
7
+ ## Usage
8
+
9
+ ```ts
10
+ import { importCommit } from "./src/commit";
11
+ import { importValidators } from "./src/validators";
12
+ import { verifyCommit } from "./src/lightclient";
13
+
14
+ // JSON from CometBFT RPC: /commit and /validators
15
+ const sh = importCommit(commitJson);
16
+ const { proto: vset, cryptoIndex } = await importValidators(validatorsJson);
17
+
18
+ const result = await verifyCommit(sh, vset, cryptoIndex);
19
+ ```
20
+
21
+ ## Tests
22
+
23
+ ```bash
24
+ $ npm run coverage
25
+
26
+ > cometbft@0.1.0 coverage
27
+ > vitest run --coverage
28
+
29
+
30
+ RUN v3.2.4 /Users/g/cometbft-ts
31
+ Coverage enabled with v8
32
+
33
+ ✓ src/tests/encoding.test.ts (6 tests) 2ms
34
+ ✓ src/tests/commit.test.ts (27 tests) 7ms
35
+ ✓ src/tests/validators.test.ts (18 tests) 13ms
36
+ ✓ src/tests/lightclient.test.ts (19 tests) 11ms
37
+
38
+ Test Files 4 passed (4)
39
+ Tests 70 passed (70)
40
+ Start at 20:45:26
41
+ Duration 377ms (transform 133ms, setup 0ms, collect 222ms, tests 33ms, environment 0ms, prepare 241ms)
42
+
43
+ % Coverage report from v8
44
+ ----------------|---------|----------|---------|---------|-------------------
45
+ File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
46
+ ----------------|---------|----------|---------|---------|-------------------
47
+ All files | 100 | 100 | 100 | 100 |
48
+ commit.ts | 100 | 100 | 100 | 100 |
49
+ encoding.ts | 100 | 100 | 100 | 100 |
50
+ lightclient.ts | 100 | 100 | 100 | 100 |
51
+ types.ts | 0 | 0 | 0 | 0 |
52
+ validators.ts | 100 | 100 | 100 | 100 |
53
+ ----------------|---------|----------|---------|---------|-------------------
54
+ ```
package/buf.gen.yaml ADDED
@@ -0,0 +1,12 @@
1
+ version: v1
2
+ plugins:
3
+ - plugin: buf.build/community/stephenh-ts-proto:v2.6.1
4
+ out: src/proto
5
+ opt:
6
+ - env=browser
7
+ - esModuleInterop=true
8
+ - useOptionals=messages
9
+ - forceLong=bigint
10
+ - snakeToCamel=true
11
+ - outputServices=none
12
+ - useDate=false
package/buf.yaml ADDED
@@ -0,0 +1,4 @@
1
+ version: v1
2
+ deps:
3
+ - buf.build/cometbft/cometbft
4
+ - buf.build/googleapis/googleapis
package/dist/.gitkeep ADDED
File without changes
@@ -0,0 +1,31 @@
1
+ import simpleImportSort from "eslint-plugin-simple-import-sort";
2
+ import * as tseslint from "typescript-eslint";
3
+
4
+ export default [
5
+ ...tseslint.configs.recommended,
6
+
7
+ {
8
+ files: ["**/*.{js,ts}"],
9
+ languageOptions: {
10
+ ecmaVersion: 2022,
11
+ sourceType: "module",
12
+ },
13
+ plugins: {
14
+ "simple-import-sort": simpleImportSort,
15
+ },
16
+ rules: {
17
+ "simple-import-sort/imports": "error",
18
+ "simple-import-sort/exports": "error",
19
+ "@typescript-eslint/no-non-null-assertion": "warn",
20
+ "@typescript-eslint/no-unused-vars": [
21
+ "error",
22
+ {
23
+ argsIgnorePattern: "^_",
24
+ varsIgnorePattern: "^_",
25
+ },
26
+ ],
27
+
28
+ "no-delete-var": "off",
29
+ },
30
+ },
31
+ ];
package/localnet.sh ADDED
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # ===================== config =====================
5
+ COMETBFT_BIN="${COMETBFT_BIN:-cometbft}" # path to cometbft binary
6
+ HOME_ROOT="${HOME_ROOT:-./mytestnet}"
7
+ NODES=4
8
+
9
+ # base ports (node0 uses these; others add STEP)
10
+ RPC_BASE=26657
11
+ P2P_BASE=26656
12
+ PPROF_BASE=6060
13
+ STEP=3
14
+
15
+ node_home() { echo "${HOME_ROOT}/node$1"; }
16
+ rpc_port() { echo $((RPC_BASE + ($1 * STEP))); }
17
+ p2p_port() { echo $((P2P_BASE + ($1 * STEP))); }
18
+ pprof_port(){ echo $((PPROF_BASE+ ($1 * STEP))); }
19
+
20
+ die() { echo "error: $*" >&2; exit 1; }
21
+ has() { command -v "$1" >/dev/null 2>&1; }
22
+
23
+ # ===================== portable sed =====================
24
+ # GNU vs BSD sed inline edit
25
+ sedi() {
26
+ if sed --version >/dev/null 2>&1; then
27
+ sed -i -e "$@"
28
+ else
29
+ sed -i '' -e "$@"
30
+ fi
31
+ }
32
+
33
+ # set TOML key within a section [section]; quoted value
34
+ toml_set_q() {
35
+ local file="$1" section="$2" key="$3" value="$4"
36
+ sedi "/^\[${section}\]\$/,/^\[/{ s|^${key}[[:space:]]*=.*$|${key} = \"${value}\"|; }" "$file"
37
+ }
38
+
39
+ # set TOML key within a section [section]; raw (booleans/numbers)
40
+ toml_set_raw() {
41
+ local file="$1" section="$2" key="$3" value="$4"
42
+ sedi "/^\[${section}\]\$/,/^\[/{ s|^${key}[[:space:]]*=.*$|${key} = ${value}|; }" "$file"
43
+ }
44
+
45
+ # ===================== generation & patching =====================
46
+ ensure_tools() {
47
+ has "$COMETBFT_BIN" || die "cometbft not found; set COMETBFT_BIN or add to PATH"
48
+ has curl || die "curl required"
49
+ has jq || die "jq required (brew install jq)"
50
+ }
51
+
52
+ gen_testnet() {
53
+ # nuke old, generate, THEN make logs/run dirs so they survive
54
+ rm -rf "$HOME_ROOT"
55
+ "$COMETBFT_BIN" testnet --v "$NODES" --o "$HOME_ROOT" >/dev/null
56
+ mkdir -p "${HOME_ROOT}/_logs" "${HOME_ROOT}/_run"
57
+ echo "Generated ${NODES}-node testnet in $HOME_ROOT"
58
+ }
59
+
60
+ patch_node_config() {
61
+ local i="$1"
62
+ local cfg="$(node_home "$i")/config/config.toml"
63
+
64
+ toml_set_q "$cfg" rpc laddr "tcp://127.0.0.1:$(rpc_port "$i")"
65
+ toml_set_q "$cfg" p2p laddr "tcp://127.0.0.1:$(p2p_port "$i")"
66
+ toml_set_q "$cfg" instrumentation pprof_laddr "localhost:$(pprof_port "$i")"
67
+
68
+ # localhost-friendly P2P
69
+ toml_set_raw "$cfg" p2p addr_book_strict false
70
+ toml_set_raw "$cfg" p2p allow_duplicate_ip true
71
+ }
72
+
73
+ wire_peers() {
74
+ # gather ids
75
+ local -a ids
76
+ for i in $(seq 0 $((NODES-1))); do
77
+ ids[$i]=$("$COMETBFT_BIN" show-node-id --home "$(node_home "$i")")
78
+ done
79
+ # set persistent_peers (everyone else)
80
+ for i in $(seq 0 $((NODES-1))); do
81
+ local peers=""
82
+ for j in $(seq 0 $((NODES-1))); do
83
+ [ "$i" -eq "$j" ] && continue
84
+ local addr="${ids[$j]}@127.0.0.1:$(p2p_port "$j")"
85
+ peers="${peers:+$peers,}$addr"
86
+ done
87
+ toml_set_q "$(node_home "$i")/config/config.toml" p2p persistent_peers "$peers"
88
+ done
89
+ }
90
+
91
+ # ===================== lifecycle =====================
92
+ start_nodes() {
93
+ for i in $(seq 0 $((NODES-1))); do
94
+ local home log pidfile
95
+ home="$(node_home "$i")"
96
+ log="${HOME_ROOT}/_logs/node${i}.log"
97
+ pidfile="${HOME_ROOT}/_run/node${i}.pid"
98
+
99
+ if [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
100
+ echo "node${i} already running (pid $(cat "$pidfile"))"
101
+ continue
102
+ fi
103
+
104
+ echo "Starting node${i} (RPC :$(rpc_port "$i"), P2P :$(p2p_port "$i")) ..."
105
+ nohup "$COMETBFT_BIN" node \
106
+ --home "$home" \
107
+ --proxy_app=persistent_kvstore \
108
+ >"$log" 2>&1 &
109
+ echo $! >"$pidfile"
110
+ done
111
+
112
+ # wait for RPC to be responsive
113
+ for i in $(seq 0 $((NODES-1))); do
114
+ local port; port=$(rpc_port "$i")
115
+ printf "Waiting for node%s RPC :%s " "$i" "$port"
116
+ for _ in $(seq 1 60); do
117
+ if curl -sf "http://127.0.0.1:${port}/status" >/dev/null; then
118
+ echo "✓"
119
+ break
120
+ fi
121
+ sleep 0.2; printf "."
122
+ done
123
+ echo
124
+ if ! curl -sf "http://127.0.0.1:${port}/status" >/dev/null; then
125
+ echo "node${i} failed to start; last 50 log lines:"
126
+ tail -n 50 "${HOME_ROOT}/_logs/node${i}.log" || true
127
+ exit 1
128
+ fi
129
+ done
130
+ echo "All nodes up."
131
+ }
132
+
133
+ stop_nodes() {
134
+ local any=0
135
+ for i in $(seq 0 $((NODES-1))); do
136
+ local pidfile="${HOME_ROOT}/_run/node${i}.pid"
137
+ if [ -f "$pidfile" ]; then
138
+ local pid; pid="$(cat "$pidfile")"
139
+ if kill -0 "$pid" 2>/dev/null; then
140
+ echo "Stopping node${i} (pid $pid)"
141
+ kill "$pid" || true
142
+ any=1
143
+ fi
144
+ rm -f "$pidfile"
145
+ fi
146
+ done
147
+ [ "$any" -eq 0 ] && echo "No nodes appeared to be running."
148
+ }
149
+
150
+ status_nodes() {
151
+ for i in $(seq 0 $((NODES-1))); do
152
+ local pidfile="${HOME_ROOT}/_run/node${i}.pid"
153
+ local state="stopped"
154
+ if [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
155
+ state="RUNNING (pid $(cat "$pidfile"))"
156
+ fi
157
+ local port; port=$(rpc_port "$i")
158
+ local h="n/a"
159
+ if curl -sf "http://127.0.0.1:${port}/status" >/dev/null 2>&1; then
160
+ h=$(curl -s "http://127.0.0.1:${port}/status" | jq -r .result.sync_info.latest_block_height)
161
+ fi
162
+ echo "node${i}: ${state}, RPC :$port, height=${h}"
163
+ done
164
+ }
165
+
166
+ logs_node() {
167
+ local i="${1:-0}"
168
+ tail -f "${HOME_ROOT}/_logs/node${i}.log"
169
+ }
170
+
171
+ clean_all() {
172
+ stop_nodes || true
173
+ rm -rf "$HOME_ROOT"
174
+ echo "Cleaned $HOME_ROOT"
175
+ }
176
+
177
+ send_tx() {
178
+ local port="${1:-26657}"
179
+ local data="${2:-a=1}"
180
+ curl -s "http://127.0.0.1:${port}/broadcast_tx_commit?tx=\"${data}\"" | jq .
181
+ }
182
+
183
+ snapshot() {
184
+ local port="${1:-26657}"
185
+ local H="${2:-$(curl -s 127.0.0.1:${port}/status | jq -r .result.sync_info.latest_block_height)}"
186
+ curl -s "http://127.0.0.1:${port}/commit?height=${H}" > "commit-${H}.json"
187
+ curl -s "http://127.0.0.1:${port}/validators?height=${H}" > "validators-${H}.json"
188
+ echo "Wrote commit-${H}.json and validators-${H}.json"
189
+ }
190
+
191
+ # ===================== CLI =====================
192
+ case "${1:-}" in
193
+ start)
194
+ ensure_tools
195
+ gen_testnet
196
+ for i in $(seq 0 $((NODES-1))); do patch_node_config "$i"; done
197
+ wire_peers
198
+ start_nodes
199
+ ;;
200
+ stop) stop_nodes ;;
201
+ status) status_nodes ;;
202
+ logs) logs_node "${2:-0}" ;;
203
+ clean) clean_all ;;
204
+ tx) send_tx "${2:-26657}" "${3:-a=1}" ;;
205
+ snapshot) snapshot "${2:-26657}" "${3:-}" ;;
206
+ *)
207
+ cat <<EOF
208
+ Usage: $0 {start|stop|status|logs [i]|clean|tx [rpc_port [kv]]|snapshot [rpc_port [height]]}
209
+
210
+ start Generate & start 4-node localhost net (in-process kvstore)
211
+ stop Stop all nodes
212
+ status Show PID / RPC / height
213
+ logs [i] Tail logs for node i (default 0)
214
+ clean Stop & remove the testnet directory
215
+ tx [p d] Broadcast kv tx to RPC port p (default 26657) with data d (default "a=1")
216
+ snapshot [p H] Export commit-H.json and validators-H.json from RPC port p (defaults to latest)
217
+ EOF
218
+ ;;
219
+ esac
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@freedomofpress/cometbft",
3
+ "version": "0.1.0",
4
+ "description": "A CometBFT light client for the browser.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/freedomofpress/cometbft-ts"
8
+ },
9
+ "main": "dist/client.js",
10
+ "types": "dist/client.d.ts",
11
+ "scripts": {
12
+ "proto:gen": "buf generate buf.build/cometbft/cometbft --path cometbft/types/v1/types.proto --path cometbft/types/v1/canonical.proto --path cometbft/types/v1/validator.proto --path cometbft/version/v1/types.proto",
13
+ "build": "tsc",
14
+ "test": "vitest run",
15
+ "coverage": "vitest run --coverage",
16
+ "lint": "npx eslint --ignore-pattern '**/*.test.ts' . --fix && npx prettier --write ."
17
+ },
18
+ "keywords": [
19
+ "cometbft",
20
+ "ed25519",
21
+ "p2p",
22
+ "browser",
23
+ "typescript"
24
+ ],
25
+ "author": "Giulio B",
26
+ "license": "MIT",
27
+ "homepage": "https://github.com/freedomofpress/cometbft-ts#readme",
28
+ "bugs": "https://github.com/freedomofpress/cometbft-ts/issues",
29
+ "devDependencies": {
30
+ "@vitest/coverage-v8": "^4.0.13",
31
+ "eslint-plugin-simple-import-sort": "^12.1.1",
32
+ "typescript": "^5.9.3",
33
+ "typescript-eslint": "^8.48.0",
34
+ "vitest": "^4.0.13"
35
+ },
36
+ "dependencies": {
37
+ "@bufbuild/protobuf": "^2.10.1"
38
+ }
39
+ }