Skip to content

fix: address remaining SDK findings from the 0.14 audit - #460

Closed
mr-zwets wants to merge 3 commits into
nextfrom
fix/sdk-audit-findings
Closed

mr-zwets wants to merge 3 commits into
nextfrom
fix/sdk-audit-findings

Conversation

@mr-zwets

Copy link
Copy Markdown
Member

Opened on behalf of Mathieu G. (mr-zwets), written by Claude Opus 5.5.

Fixes the remaining SDK findings from the 0.14 pre-release audit, apart from the timelock items in #455. Several of them made valid transactions fail in debug()/send(), silently burned tokens, or silently produced a different bytecode.

Changes

  • Debugging:
    • Each input is now debugged with its own contract's artifact, looked up by exact unlocking script id. Before, the lookup was by contract-name prefix, so with Vault and VaultSidecar in one transaction the sidecar input could use Vault's artifact: logs printed for code that didn't run, and a failing require crashed the debugger.
    • A failing final require(f(x)) with a non-inlined f is now reported at its own line and message.
    • console.log statements after a failing instruction are no longer printed (twice), and a log after a division by zero no longer makes debug() throw a plain Error.
    • A function whose body is only a parameter type check (require(b) for bool b) no longer crashes debug(), send() and getBitauthUri().
  • send() (breaking): a NetworkProviderError is thrown as it is instead of being wrapped in a FailedTransactionError, so the documented error classes can be caught. Unrecognised Electrum rejections are a NetworkProviderError, and MockNetworkProvider throws NetworkProviderMissingInputsError for missing or spent UTXOs. Code that caught FailedTransactionError for network rejections needs to catch NetworkProviderError as well; the release notes mark this as breaking and the migration notes show the change.
  • addBchChangeOutputIfNeeded(): ECDSA signatures are sized at their maximum length, since re-signing after adding the change output could push the fee below 1 sat/byte (about 1 in 4 builds with one ECDSA input). The docs note that placeholder inputs assume Schnorr.
  • addOpReturnOutput(): invalid hex ('0xabc', '0xzz') now throws instead of being mis-encoded.
  • Hex case: token categories and locking bytecode are compared case-insensitively. An upper case category passed to addTokenChangeOutputIfNeeded() used to add no change output, so with allowImplicitFungibleTokenBurn the tokens were burned.
  • asmToBytecode: unknown opcode names (previously encoded as OP_0) and invalid data tokens now throw, so an artifact with an opcode the installed libauth doesn't know no longer silently gets a different address.
  • Docs, release notes and migration notes.

Tests

  • Prefix-named contracts in both input orders (logs and the failing require).
  • A final require(f(x)) with disableInlining, logs after a failed require and after a division by zero, and a parameter-check-only function through the SDK.
  • A double spend on MockNetworkProvider and a fake Electrum client, both through send().
  • 100 ECDSA change builds at 1 sat/byte.
  • Invalid OP_RETURN hex, upper case token category and locking bytecode.
  • Unknown opcodes and invalid data in asmToBytecode.

Each of these tests fails on the current next.

Merges cleanly with #457 and #458; with #455 only TransactionBuilder.test.ts needs both sets of tests kept, and the release notes need their lines combined with the compiler findings PR.

yarn build, yarn test, yarn lint and yarn spellcheck pass.

🤖 Generated with Claude Code

rkalis and others added 3 commits September 26, 2026 21:40
- Reject hex string arguments with an odd number of digits or non-hex
  characters, which silently encoded to different bytes, and check that
  `pubkey` arguments are 33 or 65 bytes
- Fix the ElectrumNetworkProvider no longer connecting for new requests after
  a failed request, when using automatic connection management
- Compare the exact fee per byte against the minimum and maximum, instead of
  the fee per byte rounded to two decimals
- Reject `setLocktime()` values that are not an unsigned 32-bit integer
- Don't throw a FailedTransactionError from `send()` when the transaction was
  broadcast, but could not be retrieved afterwards
- Report a failing final `require(variable)` at the require's own line, when
  its opcodes are optimised away
- MockNetworkProvider: validate transactions when `updateUtxoSet` is false,
  reject block height locktimes above the mock block height, and compare hex
  strings case-insensitively
- Split the test matcher types into cashscript/vitest (Vitest 5's `Matchers`)
  and cashscript/jest (`jest.Matchers`), since Vitest 5 no longer reads the
  jest namespace

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
cashscript Ready Ready Preview Sep 28, 2026 10:11pm UTC

Request Review

@mr-zwets

Copy link
Copy Markdown
Member Author

On behalf of Mathieu G. (mr-zwets), written by Claude Opus 5.5. Opened from the wrong branch by mistake (the name collided with the branch of #454), please ignore; replaced by a new PR.

This branch was successfully deployed

1 active deployment
Preview — 5e8b55ac Deployed Sep 28, 2026 by vercel[bot]
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.

2 participants