Skip to content

perf: simplify counter packing and dynamic truncation in generateOTP - #284

Open
pataar wants to merge 2 commits into
Spomky-Labs:11.6.xfrom
pataar:perf/pack-and-direct-ord-truncation
Open

perf: simplify counter packing and dynamic truncation in generateOTP#284
pataar wants to merge 2 commits into
Spomky-Labs:11.6.xfrom
pataar:perf/pack-and-direct-ord-truncation

Conversation

@pataar

@pataar pataar commented Jun 10, 2026

Copy link
Copy Markdown

Summary

Two byte-equivalent simplifications inside OTP::generateOTP(). No behavior change.

  • intToByteString()pack('J', $input). J is unsigned 64-bit big-endian — exactly the RFC 4226 counter encoding. Drops a loop, an array, array_reverse, implode, and str_pad per generation. The private helper is removed.
  • unpack('C*', $hash) + array_values → direct ord() reads on the five bytes the truncation actually uses (last byte for offset, then four at that offset). Removes a ~20-entry int array allocation per generation.

Why it's safe

Verified byte-equivalence locally: intToByteString vs pack('J') match for 0, RFC timecodes, values > 2³², and PHP_INT_MAX; old vs new truncation match across 3000 hashes spanning SHA1/256/512. The RFC 6238 Appendix B vectors in TOTPTest::dataVectors plus the at(0) edge case (where the old while ($int !== 0) loop never iterated) exercise the full pipeline.

Verified locally: PHPUnit (160 tests, 440 assertions), PHPStan, ECS, Rector, parallel-lint — all clean.

pataar added 2 commits June 10, 2026 11:20
- Replace the hand-rolled intToByteString() with pack('J', $input).
  'J' is unsigned 64-bit big-endian, which is exactly the counter
  encoding required by RFC 4226. Drops a loop, an array, array_reverse,
  implode, and str_pad per OTP generation. Byte-equivalent for all
  inputs including 0, values > 2^32, and PHP_INT_MAX.

- Replace unpack('C*', $hash) + array_values with direct ord() reads on
  the five bytes the truncation actually uses (last byte for the offset,
  then four bytes at that offset). Removes the allocation of a ~20-entry
  int array per generation.

Behavior is unchanged: both pieces produce byte-identical output to the
previous implementation. RFC 4226 and RFC 6238 Appendix B vector tests
in tests/HOTPTest.php and tests/TOTPTest.php cover the full pipeline
across SHA1, SHA256, and SHA512.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant