Hashing.sha256 digests tx content as binary, not UTF-8 → Transaction hash mismatch on any non-ASCII tx
Impact: any transaction whose content carries a non-ASCII character (em dash, curly quote, accented letter, emoji, any UTF-8 > 0x7F) fails client-side confirm with [Tx Validation] [SIGNATURE ERROR] Transaction hash mismatch. Pure-ASCII transactions are unaffected, so this hides easily until real-world content (names, feed text, descriptions) flows through — storage-program payloads, memos, listing/agreement bodies, etc.
Root cause: build/encryption/Hashing.js
static sha256(message) {
const md = forge.sha256.create();
md.update(message); // ← no encoding arg → node-forge treats the string as raw/binary
return md.digest().toHex();
}
Demos.sign() (build/websdk/demosclass.js:548) sets raw_tx.hash = Hashing.sha256(serialized). node-forge's md.update(str) without a second arg interprets str as a binary string (one byte per code unit, high bytes mangled), so for any code point > 0x7F the client hash is computed over corrupted bytes. The validating node re-hashes the received JSON as UTF-8, so the two digests disagree.
Corroboration that UTF-8 is the intended wire encoding: the SDK's own (deprecated) signer build/websdk/utils/sha256.js hashes via TextEncoder (UTF-8), and a live devnet node accepts UTF-8-hashed txs and rejects binary-hashed ones. So Hashing.sha256 is the outlier.
Fix — one line:
md.update(message, "utf8");
Verified live: with this applied, previously-failing non-ASCII payloads confirm and broadcast normally; all-ASCII behaviour is unchanged. The other Hashing.sha256 call sites we found are ASCII-only auth strings, so the change is inert for them.
Minimal repro: sign+confirm any tx whose serialized content contains — (U+2014). Fails before the patch, passes after. String vs object payload, program-name characters, wallet/instance/connect-order are all irrelevant — the single trigger is a non-ASCII byte in tx.content.
Happy to open a PR against demosdk if that's the easier path — flagging here since this is the repo we file websdk issues on. This one's worth a point release: it silently breaks settlement for any real-world string content.
Hashing.sha256digests tx content as binary, not UTF-8 →Transaction hash mismatchon any non-ASCII txImpact: any transaction whose
contentcarries a non-ASCII character (em dash, curly quote, accented letter, emoji, any UTF-8 > 0x7F) fails client-sideconfirmwith[Tx Validation] [SIGNATURE ERROR] Transaction hash mismatch. Pure-ASCII transactions are unaffected, so this hides easily until real-world content (names, feed text, descriptions) flows through — storage-program payloads, memos, listing/agreement bodies, etc.Root cause:
build/encryption/Hashing.jsDemos.sign()(build/websdk/demosclass.js:548) setsraw_tx.hash = Hashing.sha256(serialized). node-forge'smd.update(str)without a second arg interpretsstras a binary string (one byte per code unit, high bytes mangled), so for any code point > 0x7F the client hash is computed over corrupted bytes. The validating node re-hashes the received JSON as UTF-8, so the two digests disagree.Corroboration that UTF-8 is the intended wire encoding: the SDK's own (deprecated) signer
build/websdk/utils/sha256.jshashes viaTextEncoder(UTF-8), and a live devnet node accepts UTF-8-hashed txs and rejects binary-hashed ones. SoHashing.sha256is the outlier.Fix — one line:
Verified live: with this applied, previously-failing non-ASCII payloads confirm and broadcast normally; all-ASCII behaviour is unchanged. The other
Hashing.sha256call sites we found are ASCII-only auth strings, so the change is inert for them.Minimal repro: sign+confirm any tx whose serialized content contains
—(U+2014). Fails before the patch, passes after. String vs object payload, program-name characters, wallet/instance/connect-order are all irrelevant — the single trigger is a non-ASCII byte intx.content.Happy to open a PR against demosdk if that's the easier path — flagging here since this is the repo we file websdk issues on. This one's worth a point release: it silently breaks settlement for any real-world string content.