📢 @steemit/[email protected] — Server-side transaction signature verification is finally usable
Release channel: next (dist-tag @next on npm)
Version: 1.0.20
Date: 2026-07-15
Install: npm install @steemit/steem-js@next
Note: the
latesttag still points to the legacy0.8.0. The 2025 rewrite
ships under thenexttag, so use@steemit/steem-js@next(or pin
@steemit/[email protected]) to get this release.
Headline
steem.auth.verifyTransaction is fixed and steem.auth.serializeTransaction is
now exported. Wallets, relay services, and backends can finally perform
cryptographic verification that a signed transaction was produced by the claimed
account's key — before forwarding it to the chain.
What was broken
steem.auth.verifyTransaction() verified signatures against
Buffer.from(JSON.stringify(transaction)). But signTransaction signs over the
binary digest sha256(chain_id ‖ serializeTransaction(trx)). The two never matched,
so verifyTransaction returned false for every legitimately-signed
transaction — the function was completely unusable.
This blocked any downstream service from doing real server-side signature
verification; the only check available was transaction-shape validation, with
the actual cryptographic check deferred to chain-level rejection on broadcast.
What changed in 1.0.20
Fixed — verifyTransaction uses the correct digest
verifyTransaction(transaction, publicKey) now reconstructs the exact digest that
signTransaction signs over, and verifies each signature against it:
digest = sha256(chain_id ‖ serializeTransaction(normalizedTrx))
Correctness details:
- Mirrors
signTransaction's serialization path exactly (transaction.toBuffer). - The
signaturesfield is excluded from the digest —signTransaction
serializes before attaching signatures, so verification stripssignatures
before serializing too. - Returns
trueif any signature is valid forpublicKey; otherwisefalse.
Added — serializeTransaction exported
steem.auth.serializeTransaction(trx) returns the binary Buffer of a
transaction, identical to what signTransaction uses internally. Downstream apps
can now rebuild the signing digest themselves:
import { sha256 } from '@noble/hashes/sha2';
const buf = steem.auth.serializeTransaction(tx); // Buffer
const chainId = Buffer.alloc(32, 0); // mainnet chain_id (all-zero)
const digest = Buffer.from(sha256(Buffer.concat([chainId, buf])));
Type declarations are emitted automatically by the TypeScript build
(dist/auth/index.d.ts).
Quick start — sign → verify round-trip
const { steem } = require('@steemit/steem-js');
const wif = '5JLw5dgQAx6rhZEgNN5C2ds1V47RweGshynFSWFbaMohsYsBvE8';
const publicKey = steem.auth.wifToPublic(wif);
const tx = {
ref_block_num: 123,
ref_block_prefix: 456789,
expiration: '2026-07-10T00:00:00',
operations: [['transfer', {
from: 'alice', to: 'bob', amount: '1.000 STEEM', memo: ''
}]],
extensions: [],
};
// Client signs locally
const signedTx = steem.auth.signTransaction(tx, [wif]);
// Server verifies before relaying
const isValid = steem.auth.verifyTransaction(signedTx, publicKey);
console.log(isValid); // true
// Wrong key / tampered tx / missing signatures → false
Security use case — relay/wallet server verification
This release closes a defense-in-depth gap for services that relay signed
transactions on behalf of clients. A wallet backend can now, on receipt of a
client-submitted signed transaction, independently prove the signature belongs to
the claimed account's key before broadcasting:
function verifyClientTransaction(signedTx, expectedPublicKey) {
if (!signedTx?.signatures?.length) return false;
return steem.auth.verifyTransaction(signedTx, expectedPublicKey);
}
// In a relay route:
// const account = await steem.api.getAccountsAsync([username]);
// const expectedPubKey = account[0].active.key_auths[0][0];
// if (!verifyClientTransaction(clientSignedTx, expectedPubKey)) {
// return res.status(403).json({ error: 'Invalid signature' });
// }
// await steem.broadcast.sendAsync(clientSignedTx);
Previously such a server could only validate transaction shape (signatures
present, finite ref_block fields, non-empty operations); it could not
cryptographically prove the signer. This was tracked as a deferred Critical
finding in the wallet security audit (mitigated to Low by per-route
operation-type enforcement + HMAC CSRF, but not fully closed until this upstream
fix shipped).
Also in this release
- Build fix (#540): the
[email protected]patch (which eliminates the Node 20+
new Buffer()DEP0005 deprecation warning, originally landed in 1.0.17) had
silently regressed — pnpm 10+ stopped readingpatchedDependenciesfrom
package.json. It is migrated topnpm-workspace.yamland re-applied;dist/
no longer emitsnew Buffer().
Documentation
docs/README.md—
Authentication section: newverifyTransaction/serializeTransactionentries
with params and runnable examples.docs/signature-verification-examples.md—
new "Transaction Signature Verification" section (round-trip, server-side
relay verification, rejected cases, manual digest construction).
Upgrade
npm install @steemit/steem-js@next
# or pin exactly:
npm install @steemit/[email protected]
No breaking changes. signTransaction is untouched; cross-language serialization
fixtures are unchanged and remain green.
Links
- GitHub: https://github.com/steemit/steem-js
- Changelog:
CHANGELOG.md - PR #539 —
verifyTransactiondigest fix +serializeTransactionexport - PR #540 —
patchedDependenciesmigration (build regression fix)
Upvoted! Thank you for supporting witness @jswit.