Files
pangolin/pay/internal/tron/sign_test.go
T
wangjia 7cf87a764f feat(pay): Phase D 归集 —— 气隙签名 sweep(#34/34A)
动钱代码,按"联网建/广播 + 离线签"气隙流程,离线签名独立验签防篡改:
- wallet: DecodeTronAddress/TronAddressBodyHex(base58check 解码校验)、AddressFromMnemonic(离线校验用)。
- tron/sign: TxID(sha256 raw_data)、SignRawData(secp256k1 → R||S||recid 65B)、RecoverAddressBody。
- tron/abi: ABIEncodeTransferParams(transfer(address,uint256) 参数)、keccak 地址体。
- tron/tx(联网): BuildTransfer(triggersmartcontract 建未签名)、TRC20Balance、Broadcast。
- cmd/sweep: plan/build/sign/broadcast 四段;sign 对每笔独立验:①重算 txid 防篡改 ②收款人+额 ABI
  内嵌 ③USDT 合约内嵌 ④派生地址==owner,任一不符拒签;助记词只经 PAY_SWEEP_MNEMONIC(离线机,不入 arg)。
- 测试:签名可恢复到正确地址、ABI 编码、地址解码 roundtrip 全绿。
- 联网上链部分标注需 Phase E 真链验证;README 补 sweep 用法 + 验签说明 + gas 提醒。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 04:12:04 +08:00

77 lines
2.3 KiB
Go

package tron
import (
"encoding/hex"
"math/big"
"testing"
"github.com/wangjia/pangolin/pay/internal/wallet"
)
const testMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
// addr index 0 of the test mnemonic (see wallet golden vector).
const testAddr0 = "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH"
// TestSignRecoverRoundtrip proves the signing path is correct: signing raw_data
// with address 0's private key yields a signature that recovers to address 0.
// This is the crux of the money-moving path — if it holds, TRON will accept the
// signature as coming from the owner.
func TestSignRecoverRoundtrip(t *testing.T) {
priv, err := wallet.PrivKeyHexFromMnemonic(testMnemonic, "", 0, 0, 0)
if err != nil {
t.Fatalf("privkey: %v", err)
}
rawHex := "0a0212340a0212341234567890abcdef" // arbitrary non-empty raw_data
sig, err := SignRawData(rawHex, priv)
if err != nil {
t.Fatalf("sign: %v", err)
}
if len(sig) != 130 { // 65 bytes == 130 hex chars
t.Fatalf("signature hex len %d, want 130", len(sig))
}
body, err := RecoverAddressBody(rawHex, sig)
if err != nil {
t.Fatalf("recover: %v", err)
}
wantBody, _ := wallet.TronAddressBodyHex(testAddr0)
if hex.EncodeToString(body) != wantBody {
t.Fatalf("recovered body %x != address 0 body %s", body, wantBody)
}
}
func TestABIEncodeTransferParams(t *testing.T) {
p, err := ABIEncodeTransferParams(testAddr0, 5_000000)
if err != nil {
t.Fatalf("abi: %v", err)
}
if len(p) != 128 { // 64 bytes == 128 hex chars
t.Fatalf("param hex len %d, want 128", len(p))
}
// bytes[12:32] must equal the 20-byte address body.
wantBody, _ := wallet.TronAddressBodyHex(testAddr0)
if p[24:64] != wantBody {
t.Fatalf("recipient word %s != body %s", p[24:64], wantBody)
}
// bytes[0:12] must be zero padding.
if p[0:24] != "000000000000000000000000" {
t.Fatalf("recipient not left-padded: %s", p[0:24])
}
// amount word must decode to 5000000.
amt, ok := new(big.Int).SetString(p[64:128], 16)
if !ok || amt.Int64() != 5_000000 {
t.Fatalf("amount word decodes to %v, want 5000000", amt)
}
}
func TestABIEncodeRejectsBad(t *testing.T) {
if _, err := ABIEncodeTransferParams(testAddr0, 0); err == nil {
t.Fatal("expected error on zero amount")
}
if _, err := ABIEncodeTransferParams("garbage", 1); err == nil {
t.Fatal("expected error on bad address")
}
}