package tron import ( "crypto/sha256" "encoding/hex" "fmt" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" ) // TxID computes the TRON transaction id: sha256 of the raw_data bytes. The // signature is made over this hash. Recomputing it offline from raw_data_hex // (rather than trusting a txID handed over by the online builder) is what makes // air-gapped signing safe — a tampered raw_data yields a different id. func TxID(rawDataHex string) ([]byte, error) { raw, err := hex.DecodeString(rawDataHex) if err != nil { return nil, fmt.Errorf("tron: raw_data hex: %w", err) } if len(raw) == 0 { return nil, fmt.Errorf("tron: empty raw_data") } h := sha256.Sum256(raw) return h[:], nil } // SignRawData signs raw_data with a hex private key and returns the 65-byte TRON // signature hex: R(32) || S(32) || recid(1, value 0/1). OFFLINE ONLY. func SignRawData(rawDataHex, privHex string) (string, error) { txid, err := TxID(rawDataHex) if err != nil { return "", err } pb, err := hex.DecodeString(privHex) if err != nil { return "", fmt.Errorf("tron: privkey hex: %w", err) } priv, _ := btcec.PrivKeyFromBytes(pb) // SignCompact returns 65 bytes: [header || R || S], header = 27+recid for an // uncompressed key. TRON wants R || S || recid, so rearrange. compact := ecdsa.SignCompact(priv, txid, false) if len(compact) != 65 { return "", fmt.Errorf("tron: unexpected compact signature length %d", len(compact)) } recid := compact[0] - 27 sig := make([]byte, 0, 65) sig = append(sig, compact[1:65]...) // R || S sig = append(sig, recid) // recovery id 0/1 return hex.EncodeToString(sig), nil } // RecoverAddressBody recovers the signer's 20-byte address body from a raw_data // hex + TRON signature hex — used by tests (and could verify a signature). func RecoverAddressBody(rawDataHex, sigHex string) ([]byte, error) { txid, err := TxID(rawDataHex) if err != nil { return nil, err } sig, err := hex.DecodeString(sigHex) if err != nil || len(sig) != 65 { return nil, fmt.Errorf("tron: signature must be 65 bytes hex") } // Rebuild btcec compact layout: [header=27+recid || R || S]. compact := make([]byte, 65) compact[0] = 27 + sig[64] copy(compact[1:], sig[:64]) pub, _, err := ecdsa.RecoverCompact(compact, txid) if err != nil { return nil, fmt.Errorf("tron: recover: %w", err) } return keccakAddressBody(pub.SerializeUncompressed()), nil }