package wallet import ( "strings" "testing" ) // testMnemonic is the canonical all-zero-entropy BIP39 test vector. The derived // TRON addresses logged by TestAccountXpubDeriveConsistency must match // iancoleman.io/bip39 (Coin = TRX, BIP44) — that manual comparison is Phase A.4 // of the crypto-tx-engine plan (guards against a derivation mismatch that would // silently send funds to addresses we don't control). const testMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" // TestAccountXpubDeriveConsistency proves the watcher's watch-only path // (xpub -> address) yields exactly the same address as the offline private path // (seed -> privkey -> pubkey -> address) for each index. That equality is what // guarantees every receiving address the watcher hands out is spendable by the // key we hold in cold storage. func TestAccountXpubDeriveConsistency(t *testing.T) { xpub, err := AccountXpubFromMnemonic(testMnemonic, "", 0) if err != nil { t.Fatalf("account xpub: %v", err) } t.Logf("account xpub (m/44'/195'/0'): %s", xpub) acct, err := AccountKeyFromMnemonic(testMnemonic, "", 0) if err != nil { t.Fatalf("account key: %v", err) } for i := uint32(0); i < 5; i++ { viaXpub, err := AddressFromAccountXpub(xpub, 0, i) // what the watcher does if err != nil { t.Fatalf("via xpub [%d]: %v", i, err) } ck, err := acct.Derive(0) if err != nil { t.Fatalf("derive change: %v", err) } ak, err := ck.Derive(i) if err != nil { t.Fatalf("derive index %d: %v", i, err) } pub, err := ak.ECPubKey() if err != nil { t.Fatalf("ec pubkey: %v", err) } viaPriv := PubKeyToTronAddress(pub.SerializeUncompressed()) if viaXpub != viaPriv { t.Fatalf("index %d: xpub-derived %q != priv-derived %q", i, viaXpub, viaPriv) } if !strings.HasPrefix(viaXpub, "T") || len(viaXpub) != 34 { t.Fatalf("index %d: not a valid TRON address: %q", i, viaXpub) } t.Logf("m/44'/195'/0'/0/%d -> %s", i, viaXpub) } } // TestKnownVector locks the derivation to a golden result (filled from the run of // TestAccountXpubDeriveConsistency, then confirmed against iancoleman.io — A.4). // If this ever changes, the derivation implementation regressed. func TestKnownVector(t *testing.T) { if len(goldenAddrs) == 0 { t.Skip("golden vector not yet baked — run TestAccountXpubDeriveConsistency, confirm vs Ian Coleman, then fill goldenAddrs") } xpub, err := AccountXpubFromMnemonic(testMnemonic, "", 0) if err != nil { t.Fatalf("xpub: %v", err) } if goldenXpub != "" && xpub != goldenXpub { t.Fatalf("account xpub changed:\n got %s\n want %s", xpub, goldenXpub) } for i, want := range goldenAddrs { got, err := AddressFromAccountXpub(xpub, 0, uint32(i)) if err != nil { t.Fatalf("addr[%d]: %v", i, err) } if got != want { t.Fatalf("addr[%d]: got %s want %s", i, got, want) } } } // Golden vector for the "abandon…about" test mnemonic, Coin=TRX, m/44'/195'/0'. // Locks the derivation against regression. ⚠️ MUST be confirmed once against // iancoleman.io/bip39 (Phase A.4) — self-consistency (TestAccountXpubDeriveConsistency) // proves the xpub and private paths agree, but only an independent tool proves // both aren't wrong the same way. If Ian Coleman disagrees, the impl has a bug. var ( goldenXpub = "xpub6D1AabNHCupeiLM65ZR9UStMhJ1vCpyV4XbZdyhMZBiJXALQtmn9p42VTQckoHVn8WNqS7dqnJokZHAHcHGoaQgmv8D45oNUKx6DZMNZBCd" goldenAddrs = []string{ "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH", // m/44'/195'/0'/0/0 "TSeJkUh4Qv67VNFwY8LaAxERygNdy6NQZK", // m/44'/195'/0'/0/1 "TYJPRrdB5APNeRs4R7fYZSwW3TcrTKw2gx", // m/44'/195'/0'/0/2 } )