Skip to content

fix(sdk-coin-trx): emit canonical AccountCreate raw_data_hex#8837

Open
bhavidhingra wants to merge 1 commit into
masterfrom
chalo-457-trx-account-create-canonical-encoding
Open

fix(sdk-coin-trx): emit canonical AccountCreate raw_data_hex#8837
bhavidhingra wants to merge 1 commit into
masterfrom
chalo-457-trx-account-create-canonical-encoding

Conversation

@bhavidhingra
Copy link
Copy Markdown
Contributor

Summary

  • AccountCreateContract's enum value is 0 — the proto3 default for the outer Transaction.Contract.type field. The SDK explicitly encoded it, so raw_data_hex carried a stray 2-byte 0800 tag inside the contract envelope.

  • TRON's node re-serializes raw_data from broadcast JSON under strict proto3 semantics, which omits default-valued fields. Its canonical raw_data_hex is 2 bytes shorter, with a different sha256 (= txID).

  • For TSS wallets the signature is computed over sha256(SDK_raw_data_hex) but TRON validates against sha256(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:

    SIGERROR Validate signature error: <sig> is signed by <random T…> but it is not contained of permission
    

    Hot-wallet flows sign-and-recover locally against the same buggy bytes and so don't trip this.

Why only AccountCreate

Builder type enum value Affected?
AccountCreateContract 0 (proto3 default) yes
TransferContract 1 no
FreezeBalanceV2Contract 11 no
VoteWitnessContract 4 no
DelegateResourceContract 57 no

Every 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=0 field is already present in freezeBalanceTxBuilder.ts:175-181 with an explanatory comment — this PR applies the same idea to the outer type field for AccountCreate.

Fix

In getAccountCreateTxRawDataHex, omit the explicit type field from the txContract literal so protobufjs doesn't emit the default-valued tag. The decoded protobuf still reports type = AccountCreateContract because that's the field's default value.

Also removes the now-unused ContractType import.

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 against sha256(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 asserts raw_data_hex does not contain 5a68080012 (buggy framing with the default-valued enum tag) and does contain 5a661264 (canonical framing).
  • Existing AccountCreate builder tests pass unchanged (build, sign, round-trip, extendValidTo, multi-sign, txID stability).
  • End-to-end: TSS AccountCreate broadcast on testnet for an MPC wallet should now succeed (previously failed with the SIGERROR above).

🤖 Generated with Claude Code

@bhavidhingra bhavidhingra requested a review from a team as a code owner May 22, 2026 15:37
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 22, 2026

CHALO-457

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