node-firebird 2.3.3 → 2.4.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.
Files changed (109) hide show
  1. package/README.md +544 -6
  2. package/lib/callback.d.ts +20 -0
  3. package/lib/callback.js +6 -16
  4. package/lib/firebird.msg.json +1370 -1370
  5. package/lib/gdscodes.d.ts +1519 -1520
  6. package/lib/gdscodes.js +5 -5
  7. package/lib/ieee754-decimal.d.ts +38 -0
  8. package/lib/ieee754-decimal.js +71 -118
  9. package/lib/index.d.ts +37 -338
  10. package/lib/index.js +97 -86
  11. package/lib/messages.d.ts +6 -0
  12. package/lib/messages.js +135 -162
  13. package/lib/pool.d.ts +23 -0
  14. package/lib/pool.js +34 -47
  15. package/lib/srp.d.ts +51 -0
  16. package/lib/srp.js +62 -72
  17. package/lib/types.d.ts +375 -0
  18. package/lib/types.js +22 -0
  19. package/lib/unix-crypt.d.ts +1 -0
  20. package/lib/unix-crypt.js +126 -152
  21. package/lib/utils.d.ts +16 -0
  22. package/lib/utils.js +24 -48
  23. package/lib/wire/connection.d.ts +100 -0
  24. package/lib/wire/connection.js +1029 -1009
  25. package/lib/wire/const.d.ts +567 -0
  26. package/lib/wire/const.js +620 -599
  27. package/lib/wire/database.d.ts +57 -0
  28. package/lib/wire/database.js +111 -108
  29. package/lib/wire/eventConnection.d.ts +16 -0
  30. package/lib/wire/eventConnection.js +29 -31
  31. package/lib/wire/fbEventManager.d.ts +45 -0
  32. package/lib/wire/fbEventManager.js +38 -57
  33. package/lib/wire/serialize.d.ts +97 -0
  34. package/lib/wire/serialize.js +42 -141
  35. package/lib/wire/service.d.ts +66 -0
  36. package/lib/wire/service.js +374 -401
  37. package/lib/wire/socket.d.ts +59 -0
  38. package/lib/wire/socket.js +79 -40
  39. package/lib/wire/statement.d.ts +25 -0
  40. package/lib/wire/statement.js +13 -9
  41. package/lib/wire/transaction.d.ts +21 -0
  42. package/lib/wire/transaction.js +33 -61
  43. package/lib/wire/xsqlvar.d.ts +175 -0
  44. package/lib/wire/xsqlvar.js +169 -269
  45. package/package.json +12 -4
  46. package/src/callback.ts +55 -0
  47. package/{poc/node_modules/node-firebird/lib/gdscodes.js → src/gdscodes.ts} +3 -3
  48. package/{poc/node_modules/node-firebird/lib/ieee754-decimal.js → src/ieee754-decimal.ts} +7 -14
  49. package/src/index.ts +157 -0
  50. package/{poc/node_modules/node-firebird/lib/messages.js → src/messages.ts} +163 -162
  51. package/src/pool.ts +209 -0
  52. package/{poc/node_modules/node-firebird/lib/srp.js → src/srp.ts} +74 -33
  53. package/src/types.ts +419 -0
  54. package/{poc/node_modules/node-firebird/lib/unix-crypt.js → src/unix-crypt.ts} +6 -12
  55. package/{poc/node_modules/node-firebird/lib/utils.js → src/utils.ts} +11 -20
  56. package/{poc/node_modules/node-firebird/lib/wire/connection.js → src/wire/connection.ts} +611 -129
  57. package/{poc/node_modules/node-firebird/lib/wire/const.js → src/wire/const.ts} +96 -9
  58. package/{poc/node_modules/node-firebird/lib/wire/database.js → src/wire/database.ts} +110 -44
  59. package/{poc/node_modules/node-firebird/lib/wire/eventConnection.js → src/wire/eventConnection.ts} +29 -14
  60. package/{poc/node_modules/node-firebird/lib/wire/fbEventManager.js → src/wire/fbEventManager.ts} +39 -30
  61. package/{poc/node_modules/node-firebird/lib/wire/serialize.js → src/wire/serialize.ts} +78 -59
  62. package/{poc/node_modules/node-firebird/lib/wire/service.js → src/wire/service.ts} +90 -88
  63. package/src/wire/socket.ts +262 -0
  64. package/src/wire/statement.ts +71 -0
  65. package/{poc/node_modules/node-firebird/lib/wire/transaction.js → src/wire/transaction.ts} +25 -19
  66. package/{poc/node_modules/node-firebird/lib/wire/xsqlvar.js → src/wire/xsqlvar.ts} +244 -143
  67. package/.eslintrc.json +0 -12
  68. package/.github/workflows/codeql.yml +0 -76
  69. package/.github/workflows/node.js.yml +0 -95
  70. package/BIGINT_MIGRATION.md +0 -374
  71. package/CI_DEBUGGING_GUIDE.md +0 -148
  72. package/ENCRYPTION_CALLBACK.md +0 -152
  73. package/FIREBIRD_LOG_FEATURE.md +0 -145
  74. package/MINIMAL_CHANGES_SUMMARY.md +0 -136
  75. package/PR_SUMMARY.md +0 -96
  76. package/ROADMAP.md +0 -223
  77. package/SRP_PROTOCOL.md +0 -482
  78. package/poc/README.md +0 -160
  79. package/poc/helpers.js +0 -59
  80. package/poc/node_modules/.package-lock.json +0 -14
  81. package/poc/node_modules/node-firebird/.eslintrc.json +0 -12
  82. package/poc/node_modules/node-firebird/.github/workflows/codeql.yml +0 -76
  83. package/poc/node_modules/node-firebird/.github/workflows/node.js.yml +0 -95
  84. package/poc/node_modules/node-firebird/BIGINT_MIGRATION.md +0 -374
  85. package/poc/node_modules/node-firebird/CI_DEBUGGING_GUIDE.md +0 -148
  86. package/poc/node_modules/node-firebird/ENCRYPTION_CALLBACK.md +0 -152
  87. package/poc/node_modules/node-firebird/FIREBIRD_LOG_FEATURE.md +0 -145
  88. package/poc/node_modules/node-firebird/LICENSE +0 -373
  89. package/poc/node_modules/node-firebird/MINIMAL_CHANGES_SUMMARY.md +0 -136
  90. package/poc/node_modules/node-firebird/PR_SUMMARY.md +0 -96
  91. package/poc/node_modules/node-firebird/README.md +0 -794
  92. package/poc/node_modules/node-firebird/ROADMAP.md +0 -223
  93. package/poc/node_modules/node-firebird/SRP_PROTOCOL.md +0 -482
  94. package/poc/node_modules/node-firebird/lib/callback.js +0 -38
  95. package/poc/node_modules/node-firebird/lib/gdscodes.d.ts +0 -1524
  96. package/poc/node_modules/node-firebird/lib/index.d.ts +0 -316
  97. package/poc/node_modules/node-firebird/lib/index.js +0 -128
  98. package/poc/node_modules/node-firebird/lib/pool.js +0 -108
  99. package/poc/node_modules/node-firebird/lib/wire/socket.js +0 -175
  100. package/poc/node_modules/node-firebird/lib/wire/statement.js +0 -48
  101. package/poc/node_modules/node-firebird/package.json +0 -38
  102. package/poc/node_modules/node-firebird/vitest.config.js +0 -24
  103. package/poc/package-lock.json +0 -21
  104. package/poc/package.json +0 -12
  105. package/poc/reproduce-fixed.js +0 -150
  106. package/poc/reproduce.js +0 -133
  107. package/vitest.config.js +0 -27
  108. /package/{poc/node_modules/node-firebird/lib → src}/firebird.msg +0 -0
  109. /package/{poc/node_modules/node-firebird/lib → src}/firebird.msg.json +0 -0
package/poc/helpers.js DELETED
@@ -1,59 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * Shared helpers used by reproduce.js and reproduce-fixed.js.
5
- */
6
-
7
- const net = require('net');
8
-
9
- /** Timestamp prefix for log lines (HH:MM:SS.mmm) */
10
- function ts() {
11
- return new Date().toISOString().slice(11, 23);
12
- }
13
-
14
- /** Structured log with fixed-width tag */
15
- function log(tag, msg) {
16
- console.log(`[${ts()}] ${tag.padEnd(22)} ${msg}`);
17
- }
18
-
19
- /** Compact pool state string */
20
- function poolState(pool) {
21
- return (
22
- `creating=${pool._creating} ` +
23
- `idle=${pool.pooldb.length} ` +
24
- `inuse=${pool.dbinuse} ` +
25
- `pending=${pool.pending.length} ` +
26
- `destroyed=${pool._destroyed ?? '(no flag)'}`
27
- );
28
- }
29
-
30
- /**
31
- * Starts a TCP server that accepts connections but NEVER responds to data.
32
- *
33
- * This simulates a Firebird server that:
34
- * - Completes the TCP three-way handshake (SYN / SYN-ACK / ACK)
35
- * - Receives the initial op_connect packet from the client
36
- * - Never sends op_accept_data back
37
- *
38
- * Result: node-firebird's attach() callback is never called, reproducing the
39
- * scenario where _creating is permanently incremented.
40
- */
41
- function startFakeServer(port, onReady) {
42
- const server = net.createServer((socket) => {
43
- log('fake-server', `TCP accepted from :${socket.remotePort} — will NOT respond to Firebird protocol`);
44
- socket.on('data', () => { /* receive op_connect but ignore it */ });
45
- socket.on('error', () => { /* suppress errors on forced cleanup */ });
46
- });
47
-
48
- server.on('error', (err) => {
49
- console.error(`[fake-server] Error: ${err.message}`);
50
- process.exit(1);
51
- });
52
-
53
- server.listen(port, '127.0.0.1', () => {
54
- log('fake-server', `Listening on 127.0.0.1:${port} (accepts TCP, ignores Firebird wire protocol)`);
55
- onReady(server);
56
- });
57
- }
58
-
59
- module.exports = { ts, log, poolState, startFakeServer };
@@ -1,14 +0,0 @@
1
- {
2
- "name": "node-firebird-pool-poc",
3
- "version": "1.0.0",
4
- "lockfileVersion": 3,
5
- "requires": true,
6
- "packages": {
7
- "node_modules/node-firebird": {
8
- "version": "2.3.1",
9
- "resolved": "https://registry.npmjs.org/node-firebird/-/node-firebird-2.3.1.tgz",
10
- "integrity": "sha512-3yTwiLHwgtLYRv+aS4frzsQEwHMDXkP5Z1SPM/W103TvMemVRoCIaxPYO7axnUrKhgTfPHGXzqC5umYlQail/w==",
11
- "license": "MPL-2.0"
12
- }
13
- }
14
- }
@@ -1,12 +0,0 @@
1
- {
2
- "parserOptions": {
3
- "ecmaVersion": 6,
4
- "sourceType": "module",
5
- "ecmaFeatures": {
6
- "jsx": true
7
- }
8
- },
9
- "rules": {
10
- "semi": "error"
11
- }
12
- }
@@ -1,76 +0,0 @@
1
- # For most projects, this workflow file will not need changing; you simply need
2
- # to commit it to your repository.
3
- #
4
- # You may wish to alter this file to override the set of languages analyzed,
5
- # or to provide custom queries or build logic.
6
- #
7
- # ******** NOTE ********
8
- # We have attempted to detect the languages in your repository. Please check
9
- # the `language` matrix defined below to confirm you have the correct set of
10
- # supported CodeQL languages.
11
- #
12
- name: "CodeQL"
13
-
14
- on:
15
- push:
16
- branches: [ "master" ]
17
- pull_request:
18
- # The branches below must be a subset of the branches above
19
- branches: [ "master" ]
20
- schedule:
21
- - cron: '26 2 * * 3'
22
-
23
- jobs:
24
- analyze:
25
- name: Analyze
26
- runs-on: ubuntu-latest
27
- permissions:
28
- actions: read
29
- contents: read
30
- security-events: write
31
-
32
- strategy:
33
- fail-fast: false
34
- matrix:
35
- language: [ 'javascript' ]
36
- # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37
- # Use only 'java' to analyze code written in Java, Kotlin or both
38
- # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
39
- # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
40
-
41
- steps:
42
- - name: Checkout repository
43
- uses: actions/checkout@v3
44
-
45
- # Initializes the CodeQL tools for scanning.
46
- - name: Initialize CodeQL
47
- uses: github/codeql-action/init@v2
48
- with:
49
- languages: ${{ matrix.language }}
50
- # If you wish to specify custom queries, you can do so here or in a config file.
51
- # By default, queries listed here will override any specified in a config file.
52
- # Prefix the list here with "+" to use these queries and those in the config file.
53
-
54
- # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
55
- # queries: security-extended,security-and-quality
56
-
57
-
58
- # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
59
- # If this step fails, then you should remove it and run the build manually (see below)
60
- - name: Autobuild
61
- uses: github/codeql-action/autobuild@v2
62
-
63
- # ℹ️ Command-line programs to run using the OS shell.
64
- # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
65
-
66
- # If the Autobuild fails above, remove it and uncomment the following three lines.
67
- # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
68
-
69
- # - run: |
70
- # echo "Run, Build Application using script"
71
- # ./location_of_script_within_repo/buildscript.sh
72
-
73
- - name: Perform CodeQL Analysis
74
- uses: github/codeql-action/analyze@v2
75
- with:
76
- category: "/language:${{matrix.language}}"
@@ -1,95 +0,0 @@
1
- name: CI
2
-
3
- on: [push, pull_request]
4
-
5
- jobs:
6
- build:
7
- runs-on: ubuntu-latest
8
-
9
- strategy:
10
- fail-fast: false
11
- matrix:
12
- node: [20, 22, 24]
13
- firebird: [3, 4, 5]
14
-
15
- steps:
16
- - uses: actions/checkout@v6
17
- with:
18
- fetch-depth: 10
19
-
20
- - name: Setup FirebirdSQL ${{ matrix.firebird }} container
21
- run: |
22
- # Create data directory
23
- sudo mkdir -p /firebird/data
24
- sudo chmod 755 /firebird/data
25
-
26
- # Start Firebird container
27
- docker run -d \
28
- --name firebird \
29
- -e FIREBIRD_ROOT_PASSWORD="masterkey" \
30
- -e FIREBIRD_CONF_WireCrypt="Enabled" \
31
- -e FIREBIRD_CONF_AuthServer="Legacy_Auth;Srp;Win_Sspi" \
32
- -p 3050:3050 \
33
- -v /firebird/data:/firebird/data \
34
- firebirdsql/firebird:${{ matrix.firebird }}
35
-
36
- # Wait for Firebird to be ready
37
- MAX_RETRIES=30
38
- RETRY_INTERVAL=2
39
- echo "Waiting for Firebird to start..."
40
- FIREBIRD_READY=false
41
- for i in $(seq 1 $MAX_RETRIES); do
42
- if docker exec firebird /opt/firebird/bin/isql -user SYSDBA -password masterkey -z 2>&1 | grep -q "ISQL Version"; then
43
- echo "Firebird is ready!"
44
- FIREBIRD_READY=true
45
- break
46
- fi
47
- echo "Waiting... ($i/$MAX_RETRIES)"
48
- sleep $RETRY_INTERVAL
49
- done
50
-
51
- if [ "$FIREBIRD_READY" = false ]; then
52
- echo "ERROR: Firebird failed to start within the timeout period"
53
- docker ps -a
54
- docker logs firebird
55
- exit 1
56
- fi
57
-
58
- # Verify Firebird is running
59
- docker ps -a
60
- docker logs firebird
61
-
62
- - name: Use Node.js ${{ matrix.node }}
63
- uses: actions/setup-node@v3
64
- with:
65
- node-version: ${{ matrix.node }}
66
-
67
-
68
- - name: Build
69
- shell: bash
70
- run: |
71
- npm ci
72
-
73
- - name: Test (Linux)
74
- run: |
75
- export FIREBIRD_DATA=/firebird/data
76
- export FIREBIRD_DEBUG=1
77
- npm test
78
-
79
- - name: Show Firebird log on failure
80
- if: failure()
81
- run: |
82
- echo "=========================================="
83
- echo "Firebird Server Log (last 100 lines):"
84
- echo "=========================================="
85
- docker exec firebird tail -n 100 /firebird/log/firebird.log || echo "Could not read firebird.log"
86
- echo ""
87
- echo "=========================================="
88
- echo "Docker container status:"
89
- echo "=========================================="
90
- docker ps -a
91
- echo ""
92
- echo "=========================================="
93
- echo "Docker container logs:"
94
- echo "=========================================="
95
- docker logs firebird --tail 50
@@ -1,374 +0,0 @@
1
- # BigInt Migration: Replacing `big-integer` with Native BigInt
2
-
3
- ## Overview
4
-
5
- This document describes the migration from the third-party `big-integer` npm package to JavaScript's
6
- built-in `BigInt` primitive in `node-firebird`'s SRP authentication implementation. The change
7
- removes a runtime dependency, fixes a critical authentication bug that caused connection failures, and
8
- improves SRP computation performance.
9
-
10
- ---
11
-
12
- ## Background: Why `big-integer` Was Used
13
-
14
- Firebird SRP authentication requires **1024-bit modular arithmetic** (modular exponentiation, multiplication,
15
- addition, subtraction and comparison over numbers up to ~309 decimal digits). JavaScript historically
16
- lacked a built-in arbitrary-precision integer type, so the `big-integer` library was used to fill that gap.
17
-
18
- Node.js 10.3 (May 2018) shipped native `BigInt` support as a V8 feature flag; Node.js 10.4 (June 2018)
19
- enabled it by default. Node.js 10.x became LTS ("Dubnium") in October 2018.
20
- `node-firebird` targets Node.js ≥ 10, so the `big-integer` library is now entirely redundant.
21
-
22
- ---
23
-
24
- ## The Problem: Three Root Causes of Authentication Failure
25
-
26
- ### 1. Variable Shadowing in `connection.js`
27
-
28
- `lib/wire/connection.js` contained:
29
-
30
- ```js
31
- const BigInt = require('big-integer');
32
- ```
33
-
34
- This line **shadowed the global `BigInt` constructor**. Any subsequent call to `BigInt(...)` in that
35
- file created a `big-integer` library object instead of a native primitive, including the server public-key
36
- parsing:
37
-
38
- ```js
39
- // This line used the big-integer constructor, NOT the native one
40
- public: BigInt('0x' + d.buffer.slice(keyStart).toString('utf8'))
41
- ```
42
-
43
- ### 2. Incorrect Hex Parsing by `big-integer`
44
-
45
- The `big-integer` library uses **base-10 (decimal)** by default and does **not** recognise the `0x`
46
- prefix as hexadecimal:
47
-
48
- ```js
49
- const bigInteger = require('big-integer');
50
-
51
- bigInteger('0xff') // → 0 (wrong! silently returns 0)
52
- bigInteger('0xff', 16) // → 0 (still wrong, the 0x prefix confuses the parser)
53
- bigInteger('ff', 16) // → 255 (correct, but requires stripping the prefix manually)
54
- ```
55
-
56
- Contrast with native BigInt:
57
-
58
- ```js
59
- BigInt('0xff') // → 255n (correct)
60
- BigInt('0xFF') // → 255n (correct)
61
- ```
62
-
63
- Passing `'0x' + hexKey` to the `big-integer` constructor silently produced **zero**, meaning
64
- the server's public key `B` was treated as `0n` for the rest of the handshake.
65
-
66
- ### 3. Data Corruption via Decimal/Hex Base Mismatch
67
-
68
- Even in code paths that called the `big-integer` constructor correctly (e.g. `BigInt(hexStr, 16)`),
69
- the resulting library object could corrupt data when mixed with `lib/srp.js` helpers.
70
-
71
- `toBigInt` in `lib/srp.js` converts inputs to a string and prepends `'0x'`:
72
-
73
- ```js
74
- // lib/srp.js toBigInt helper (original)
75
- const str = String(hex); // big-integer.toString() returns DECIMAL
76
- return BigInt('0x' + str); // interprets DECIMAL digits as HEX!
77
- ```
78
-
79
- Example of the corruption:
80
-
81
- | Actual value | `big-integer.toString()` | `BigInt('0x' + …)` (native) | Decimal result |
82
- |---|---|---|---|
83
- | 16 | `"16"` | `BigInt('0x16')` | **22** (wrong) |
84
- | 255 | `"255"` | `BigInt('0x255')` | **597** (wrong) |
85
- | 1024 | `"1024"` | `BigInt('0x1024')` | **4132** (wrong) |
86
-
87
- This mismatch meant the client and server computed mathematically different session keys, so
88
- the M1 proof verification always failed and the connection was rejected.
89
-
90
- ---
91
-
92
- ## The Fix: What Changed in Each File
93
-
94
- ### `lib/wire/connection.js`
95
-
96
- **Removed** the shadowing line:
97
-
98
- ```diff
99
- -const BigInt = require('big-integer');
100
- ```
101
-
102
- This one-line removal is the **core fix**. With the shadowing gone, every `BigInt(...)` call in the
103
- file correctly uses the native constructor, which properly parses `0x`-prefixed hex strings.
104
-
105
- ### `lib/srp.js`
106
-
107
- Replaced every `big-integer` method call with a native-BigInt equivalent. The SRP *algorithm* is
108
- unchanged; only the arithmetic notation changed.
109
-
110
- | Before (`big-integer`) | After (native BigInt) |
111
- |---|---|
112
- | `require('big-integer')` | *(removed)* |
113
- | `BigInt(val, 16)` | `BigInt('0x' + val)` |
114
- | `a.multiply(b)` | `a * b` |
115
- | `a.add(b)` | `a + b` |
116
- | `a.subtract(b)` | `a - b` |
117
- | `a.mod(n)` | `a % n` |
118
- | `a.modPow(e, m)` | `modPow(a, e, m)` (helper added) |
119
- | `a.lesser(b)` | `a < b` |
120
- | `BigInt.isInstance(x)` | `typeof x === 'bigint'` |
121
- | `x.toString(16)` | `x.toString(16)` *(unchanged)* |
122
-
123
- A `modPow(base, exp, mod)` helper function was added at the bottom of the file (see
124
- [The `modPow` Implementation](#the-modpow-implementation) below).
125
-
126
- The `toBigInt` helper was also updated. Previously it called `String(hex)` on its argument, which
127
- would produce a decimal string for a `big-integer` object. Now it branches on `Buffer.isBuffer`:
128
-
129
- ```js
130
- // After
131
- function toBigInt(hex) {
132
- return BigInt('0x' + (Buffer.isBuffer(hex) ? hex.toString('hex') : hex));
133
- }
134
- ```
135
-
136
- ### `test/srp.js`
137
-
138
- ```diff
139
- -const bigInt = require('big-integer');
140
-
141
- -const DEBUG_PRIVATE_KEY = bigInt('60975527035CF2AD...', 16);
142
- +const DEBUG_PRIVATE_KEY = BigInt('0x60975527035CF2AD...');
143
-
144
- -assert.ok(keys.public.equals(EXPECT_CLIENT_KEY));
145
- +assert.ok(keys.public === EXPECT_CLIENT_KEY);
146
- ```
147
-
148
- ### `package.json` and `package-lock.json`
149
-
150
- The `big-integer` dependency was removed:
151
-
152
- ```diff
153
- - "big-integer": "^1.6.51",
154
- ```
155
-
156
- This shrinks the installed package tree by one package and eliminates a maintenance burden.
157
-
158
- ---
159
-
160
- ## The `modPow` Implementation
161
-
162
- The helper implements **binary (square-and-multiply) modular exponentiation**, which avoids
163
- computing `base^exp` as a full integer before reducing modulo `mod`. This is critical: a
164
- 1024-bit base raised to a 1024-bit exponent would produce a ~2 million-bit intermediate value
165
- before reduction.
166
-
167
- ```js
168
- /**
169
- * Calculates (base ^ exp) % mod using native BigInt.
170
- * Uses the square-and-multiply (binary) algorithm for efficiency.
171
- *
172
- * @param {bigint} base
173
- * @param {bigint} exp - must be non-negative
174
- * @param {bigint} mod
175
- * @returns {bigint}
176
- */
177
- function modPow(base, exp, mod) {
178
- let result = 1n;
179
- base = base % mod; // reduce base before starting
180
- while (exp > 0n) {
181
- if (exp & 1n) { // if current bit is set
182
- result = (result * base) % mod;
183
- }
184
- base = (base * base) % mod; // square
185
- exp >>= 1n; // shift to next bit
186
- }
187
- return result;
188
- }
189
- ```
190
-
191
- **Algorithm walkthrough** for `modPow(2n, 10n, 1000n)`:
192
-
193
- | `exp` (binary) | `exp & 1n` | `result` | `base` |
194
- |---|---|---|---|
195
- | `1010` | 0 | 1 | 4 |
196
- | `101` | 1 | 4 | 16 |
197
- | `10` | 0 | 4 | 256 |
198
- | `1` | 1 | `4 * 256 % 1000 = 24` | 65536 |
199
-
200
- Result: `24`; check: `2^10 = 1024`, `1024 % 1000 = 24` ✓
201
-
202
- ### Correctness Property
203
-
204
- The algorithm satisfies the invariant: `result * base^exp ≡ base_original^exp_original (mod mod)`
205
- at every loop iteration, which ensures the final `result` (when `exp = 0`) holds the correct answer.
206
-
207
- ---
208
-
209
- ## Performance Comparison
210
-
211
- The `big-integer` library is pure JavaScript using string-based decimal arithmetic. Native BigInt
212
- uses the V8 engine's C++ arbitrary-precision integer library (based on GMP/libtommath), which applies
213
- hardware multiply instructions directly.
214
-
215
- Typical timings for one `modPow(g, a, N)` call on a 1024-bit group (measured on an M2 MacBook Pro):
216
-
217
- | Implementation | Time (approx.) |
218
- |---|---|
219
- | `big-integer` (v1.6.51) | 30–120 ms |
220
- | Native `BigInt` (Node.js 20) | 1–3 ms |
221
-
222
- For an SRP handshake, `modPow` is called 3–4 times per authentication:
223
- - `clientSeed`: 1× modPow (`A = g^a mod N`)
224
- - `clientProof`: 2× modPow (`g^x mod N`, then `(B - kg^x)^(a+ux) mod N`)
225
-
226
- The total wall-clock time for SRP drops from **~200 ms** to **~5 ms** on typical hardware. This
227
- matters most on CI runners, which are often virtualised and resource-constrained.
228
-
229
- ---
230
-
231
- ## Security Implications
232
-
233
- Replacing a pure-JavaScript library with a native implementation has the following security implications:
234
-
235
- 1. **No regression**: The same SRP-6a algorithm is implemented; only the arithmetic engine changed.
236
- 2. **Fewer supply-chain risks**: One fewer npm package means one fewer potential malicious update path.
237
- 3. **Constant-time properties**: Neither `big-integer` nor native `BigInt` provides guaranteed
238
- constant-time arithmetic, so timing side-channel attacks against SRP remain theoretically possible.
239
- This was true before and after the migration and is not specific to this change.
240
- 4. **M2 not validated**: `node-firebird` does not verify the server's M2 proof (see `SRP_PROTOCOL.md`).
241
- This is unchanged and is a separate concern.
242
-
243
- ---
244
-
245
- ## Test Private-Key Size Constraint
246
-
247
- ### Root Cause of Flaky Tests with Random 1024-bit Keys
248
-
249
- `clientSession` in `lib/srp.js` reduces the client exponent modulo `PRIME.N`:
250
-
251
- ```js
252
- var ux = (u * x) % PRIME.N;
253
- var aux = (a + ux) % PRIME.N; // ← reduction
254
- ```
255
-
256
- The `big-integer` library applied the identical reduction:
257
-
258
- ```js
259
- var ux = u.multiply(x).mod(PRIME.N);
260
- var aux = a.add(ux).mod(PRIME.N); // ← same reduction
261
- ```
262
-
263
- Both implementations are therefore **identical in behaviour**.
264
-
265
- When the client private key `a` is generated as a random 1024-bit number (128 bytes) there is a ~10%
266
- chance that `a >= PRIME.N` (since `N ≈ 0.9 × 2^1024`). Combined with `ux < N`, the sum `a + ux`
267
- can exceed `N`, causing the `% N` reduction to change the effective exponent. The server side
268
- (`serverSession`) does **not** apply the same reduction to `b`, so the two session secrets diverge.
269
-
270
- ### Why this doesn't affect real Firebird authentication
271
-
272
- In a real Firebird SRP handshake the client private key is the **only** place where `a` appears, and
273
- it enters the protocol as `A = g^a mod N`. The real Firebird server therefore only ever "sees" `A`,
274
- not `a` itself. Node.js generates `a` from `crypto.randomBytes(128)`, a 1024-bit value. When
275
- `a < N` (~90% of the time) the reduction is a no-op and auth succeeds. When `a >= N`, the effective
276
- client exponent changes and auth would fail — this is a pre-existing edge case shared by both the
277
- `big-integer` and native-BigInt implementations.
278
-
279
- ### Why tests must use small (< N) private keys
280
-
281
- The unit-test helper `serverSession` (used only for testing) mirrors what the real Firebird server
282
- does: it uses the server private key `b` **without** reduction. This means that test vectors must
283
- ensure `a + ux < N` to avoid the divergence.
284
-
285
- All test private keys in `test/srp.js` are **256-bit** values — far smaller than the 1024-bit
286
- `PRIME.N` — so `a + ux < N` always holds and every test is deterministic.
287
-
288
- ```js
289
- // ✓ correct — 256-bit key, always << PRIME.N
290
- const TEST_CLIENT_1 = BigInt('0x3138bb9bc78df27c...aedd3');
291
-
292
- // ✗ flaky — 1024-bit random key, ~12% chance of a+ux >= N
293
- var clientKeys = Srp.clientSeed(); // DO NOT use this in assertions
294
- ```
295
-
296
- ---
297
-
298
- ## Verifying the Fix
299
-
300
- ### 1. Unit Tests (offline, no Firebird required)
301
-
302
- ```bash
303
- # Run the SRP unit tests
304
- npx vitest run test/srp.js
305
- ```
306
-
307
- Expected output (all tests pass):
308
-
309
- ```
310
- ✓ test/srp.js (19 tests)
311
- ✓ hexPad helper (3)
312
- ✓ clientSeed (2)
313
- ✓ serverSeed (2)
314
- ✓ Test Srp client (12)
315
- ```
316
-
317
- ### 2. Mock-Server Tests (offline, no Firebird required)
318
-
319
- ```bash
320
- npx vitest run test/mock-server.js
321
- ```
322
-
323
- These tests run a full SRP handshake over a TCP loopback against a minimal in-process mock server,
324
- exercising FB3 (Protocol 14), FB4 (Protocol 16) and FB5 (Protocol 17) code paths.
325
-
326
- ### 3. Integration Tests (real Firebird required)
327
-
328
- Start Firebird with SRP enabled (Docker example):
329
-
330
- ```bash
331
- docker run -d \
332
- --name firebird \
333
- -e FIREBIRD_ROOT_PASSWORD="masterkey" \
334
- -e FIREBIRD_CONF_WireCrypt="Enabled" \
335
- -e FIREBIRD_CONF_AuthServer="Legacy_Auth;Srp;Win_Sspi" \
336
- -p 3050:3050 \
337
- firebirdsql/firebird:5
338
-
339
- npm test
340
- ```
341
-
342
- ### 4. Debug Timing
343
-
344
- ```bash
345
- FIREBIRD_DEBUG=1 npm test 2>&1 | grep fb-debug
346
- ```
347
-
348
- With native BigInt you should see sub-10 ms values for both operations:
349
-
350
- ```
351
- [fb-debug] srp.clientSeed: 2ms
352
- [fb-debug] srp.clientProof(sha1): 4ms
353
- ```
354
-
355
- ---
356
-
357
- ## Relationship with `SRP_PROTOCOL.md`
358
-
359
- [`SRP_PROTOCOL.md`](SRP_PROTOCOL.md) describes the full SRP wire-protocol sequence, opcodes, BLR data
360
- formats, and timing troubleshooting. This document focuses specifically on the `big-integer` →
361
- native `BigInt` migration.
362
-
363
- ---
364
-
365
- ## References
366
-
367
- - [MDN: `BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
368
- - [V8 blog: BigInt — arbitrary-precision integers in JavaScript](https://v8.dev/blog/bigint)
369
- - [npm: `big-integer`](https://www.npmjs.com/package/big-integer)
370
- - [RFC 2945: The SRP Authentication and Key Exchange System](https://www.ietf.org/rfc/rfc2945.txt)
371
- - [`lib/srp.js`](lib/srp.js) — SRP implementation
372
- - [`lib/wire/connection.js`](lib/wire/connection.js) — wire protocol / SRP handshake
373
- - [`test/srp.js`](test/srp.js) — unit tests
374
- - [`SRP_PROTOCOL.md`](SRP_PROTOCOL.md) — full SRP protocol reference