package coinglass import ( "bytes" "compress/gzip" "crypto/aes" "encoding/base64" "errors" "testing" "github.com/stretchr/testify/require" ) // helper:把 plaintext 用 V2 通用管线反向加密,产出可喂给 decryptBlob 的 base64 ciphertext。 // 形态:plaintext → '"plaintext"' → gzip → pkcs7 pad → AES-ECB → base64 func encryptForTest(t *testing.T, key []byte, plaintext string) string { t.Helper() // CoinGlass 服务器端会在 plaintext 外加引号(decryptBlob 反向 strip); // 测试也照样加引号 → 验证 strip 逻辑。 quoted := []byte(`"` + plaintext + `"`) var buf bytes.Buffer gz := gzip.NewWriter(&buf) _, err := gz.Write(quoted) require.NoError(t, err) require.NoError(t, gz.Close()) padded := pkcs7Pad(buf.Bytes(), aes.BlockSize) enc, err := aesECBEncrypt(key, padded) require.NoError(t, err) return base64.StdEncoding.EncodeToString(enc) } func TestDecryptBlob_RoundTrip(t *testing.T) { cases := []struct { name string key []byte text string }{ {"16-byte key (AES-128)", []byte("0123456789abcdef"), `{"hello":"world"}`}, {"32-byte key (AES-256)", []byte("1f68efd73f8d4921acc0dead41dd39bc"), `{"foo":42,"bar":"baz"}`}, {"empty json object", []byte("0123456789abcdef"), `{}`}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { ct := encryptForTest(t, tc.key, tc.text) got, err := decryptBlob(ct, tc.key) require.NoError(t, err) require.Equal(t, tc.text, got) }) } } func TestDecryptBlob_BadCiphertext(t *testing.T) { key := []byte("0123456789abcdef") _, err := decryptBlob("!!!not_base64!!!", key) require.Error(t, err) require.ErrorIs(t, err, ErrDecryptFail) short := base64.StdEncoding.EncodeToString([]byte("toosmall")) _, err = decryptBlob(short, key) require.ErrorIs(t, err, ErrDecryptFail) } func TestPKCS7_PadUnpadRoundTrip(t *testing.T) { cases := [][]byte{ []byte(""), []byte("a"), []byte("0123456789abcde"), // 15 → pad 1 []byte("0123456789abcdef"), // 16 → pad 16 (full block) []byte("0123456789abcdef0"), } for _, in := range cases { padded := pkcs7Pad(in, 16) require.Zero(t, len(padded)%16) out, err := pkcs7Unpad(padded, 16) require.NoError(t, err) require.Equal(t, in, out) } } func TestPKCS7_UnpadRejectsBadInput(t *testing.T) { bad := []byte("0123456789abcdef") // pad byte = 'f' (0x66 = 102 > 16) _, err := pkcs7Unpad(bad, 16) require.Error(t, err) _, err = pkcs7Unpad([]byte{}, 16) require.Error(t, err) } func TestUserKeyFromSeed_TruncatesTo16(t *testing.T) { seed := "863f08689c97435b" // v=77 constant from bundle got := userKeyFromSeed(seed) require.Len(t, got, 16) // base64("863f08689c97435b") = "ODYzZjA4Njg5Yzk3NDM1Yg==" → first 16 = "ODYzZjA4Njg5Yzk3" require.Equal(t, "ODYzZjA4Njg5Yzk3", string(got)) } func TestBodyKeyFromToken_RawASCIIFirst16(t *testing.T) { token := "dfa89d5d00cf4000extra_chars" got := bodyKeyFromToken(token) require.Len(t, got, 16) require.Equal(t, "dfa89d5d00cf4000", string(got)) } func TestDecryptUniversal_TwoStage(t *testing.T) { seed := "863f08689c97435b" // v=77 bundle constant userKey := userKeyFromSeed(seed) token := "dfa89d5d00cf4000" // exactly 16 chars (matches cg.md L79 example) bodyKey := []byte(token) plaintext := `{"success":true,"code":"0","msg":"","data":{"priceMin":110000.0}}` userHeaderB64 := encryptForTest(t, userKey, token) bodyDataB64 := encryptForTest(t, bodyKey, plaintext) got, err := DecryptUniversal(seed, userHeaderB64, bodyDataB64) require.NoError(t, err) require.JSONEq(t, plaintext, string(got)) } func TestDecryptUniversal_PropagatesDecryptFail(t *testing.T) { _, err := DecryptUniversal("seed", "!!!", "!!!") require.Error(t, err) require.True(t, errors.Is(err, ErrDecryptFail)) }