fix(sdk-coin-trx): emit canonical AccountCreate raw_data_hex#8836
Closed
bhavidhingra wants to merge 1 commit into
Closed
fix(sdk-coin-trx): emit canonical AccountCreate raw_data_hex#8836bhavidhingra wants to merge 1 commit into
bhavidhingra wants to merge 1 commit into
Conversation
AccountCreateContract's enum value is 0 — the proto3 default for the outer Transaction.Contract.type field. The SDK's protobuf encoder included this default-valued tag in the wire format (2-byte `0800` inside the contract envelope), but TRON's node re-serializes raw_data from broadcast JSON following strict proto3 semantics and omits default-valued fields. The canonical raw_data_hex is therefore 2 bytes shorter, with a different sha256 (txID). For TSS wallets this manifested as a SIGERROR on broadcast: Validate signature error: <sig> is signed by <random T...> but it is not contained of permission The TSS signature was valid for sha256(SDK_raw_data_hex), but TRON validated it against sha256(canonical_raw_data_hex). With a mismatched digest, ECDSA recovery returns an unrelated pubkey whose TRON address is not in the wallet's permission set. Hot-wallet flows didn't trip this because they sign-then-recover locally against the same buggy bytes the SDK emits. Other contract builders (Transfer=1, FreezeBalanceV2=11, VoteWitness=4, DelegateResource=57, ...) all have non-default enum values, so their SDK output matches TRON's canonical encoding by accident. AccountCreate was uniquely affected. See the analogous proto3-default guard already present in freezeBalanceTxBuilder.ts for the inner `resource: BANDWIDTH=0` field. Fix: drop the explicit `type` field from the txContract object in getAccountCreateTxRawDataHex so the encoder omits it, matching the canonical wire format TRON computes from the broadcast JSON. Includes a regression test that asserts raw_data_hex does not include the proto3-default tag and uses the canonical contract framing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
AccountCreateContract's enum value is0— the proto3 default for the outerTransaction.Contract.typefield. The SDK explicitly encoded it, soraw_data_hexcarried a stray 2-byte0800tag inside the contract envelope.TRON's node re-serializes
raw_datafrom broadcast JSON under strict proto3 semantics, which omits default-valued fields. Its canonicalraw_data_hexis 2 bytes shorter, with a differentsha256(=txID).For TSS wallets the signature is computed over
sha256(SDK_raw_data_hex)but TRON validates againstsha256(canonical_raw_data_hex). ECDSA recovery against the mismatched digest returns an unrelated pubkey, whose TRON address is not in the wallet's permission set, and broadcast fails with:Hot-wallet flows sign-and-recover locally against the same buggy bytes and so don't trip this.
Why only AccountCreate
typeenum valueEvery other commonly-used TRON contract type has a non-default enum value, so the SDK's encoding happens to match TRON's canonical re-serialization. The analogous proto3-default guard for the inner
resource: BANDWIDTH=0field is already present infreezeBalanceTxBuilder.ts:175-181with an explanatory comment — this PR applies the same idea to the outertypefield for AccountCreate.Fix
In
getAccountCreateTxRawDataHex, omit the explicittypefield from thetxContractliteral so protobufjs doesn't emit the default-valued tag. The decoded protobuf still reportstype = AccountCreateContractbecause that's the field's default value.Also removes the now-unused
ContractTypeimport.Verification
Re-encoding the failure-case inputs (real production broadcast bytes) with the fix produces the canonical 132-byte
raw_data_hex, and ECDSA-recovering the production signature againstsha256(canonical)returns exactly the address TRON's error reported (TMAFWDcE5hpDvcQVn89gGB5oACwGXZKZqV) — confirming the diagnosis is precisely the failure mechanism.Test plan
npm test -w modules/sdk-coin-trx -- --grep "proto3 default"— new regression test assertsraw_data_hexdoes not contain5a68080012(buggy framing with the default-valued enum tag) and does contain5a661264(canonical framing).🤖 Generated with Claude Code