nip44_test.go raw

   1  package nip44
   2  
   3  import (
   4  	"bytes"
   5  	"crypto/sha256"
   6  	"encoding/base64"
   7  	"encoding/hex"
   8  	"encoding/json"
   9  	"os"
  10  	"strings"
  11  	"testing"
  12  
  13  	"git.smesh.lol/nostrino/pkg/crypto/secp256k1"
  14  )
  15  
  16  // The test vectors are the official NIP-44 v2 vectors published at
  17  // https://github.com/paulmillr/nip44/blob/main/nip44.vectors.json, whose
  18  // SHA-256 is 269ed0f69e4c192512cc779e78c555090cebc7c785b609e338a62afc3ce25040
  19  // as stated in the NIP.
  20  const vectorsPath = "testdata/nip44.vectors.json"
  21  
  22  type conversationKeyVector struct {
  23  	Sec1            string `json:"sec1"`
  24  	Pub2            string `json:"pub2"`
  25  	ConversationKey string `json:"conversation_key"`
  26  }
  27  
  28  type messageKeysVector struct {
  29  	Nonce       string `json:"nonce"`
  30  	ChaChaKey   string `json:"chacha_key"`
  31  	ChaChaNonce string `json:"chacha_nonce"`
  32  	HMACKey     string `json:"hmac_key"`
  33  }
  34  
  35  type encryptDecryptVector struct {
  36  	Sec1            string `json:"sec1"`
  37  	Sec2            string `json:"sec2"`
  38  	ConversationKey string `json:"conversation_key"`
  39  	Nonce           string `json:"nonce"`
  40  	Plaintext       string `json:"plaintext"`
  41  	Payload         string `json:"payload"`
  42  }
  43  
  44  type longMessageVector struct {
  45  	ConversationKey string `json:"conversation_key"`
  46  	Nonce           string `json:"nonce"`
  47  	Pattern         string `json:"pattern"`
  48  	Repeat          int    `json:"repeat"`
  49  	PlaintextSHA256 string `json:"plaintext_sha256"`
  50  	PayloadSHA256   string `json:"payload_sha256"`
  51  }
  52  
  53  type invalidDecryptVector struct {
  54  	ConversationKey string `json:"conversation_key"`
  55  	Nonce           string `json:"nonce"`
  56  	Plaintext       string `json:"plaintext"`
  57  	Payload         string `json:"payload"`
  58  	Note            string `json:"note"`
  59  }
  60  
  61  type invalidConversationKeyVector struct {
  62  	Sec1 string `json:"sec1"`
  63  	Pub2 string `json:"pub2"`
  64  	Note string `json:"note"`
  65  }
  66  
  67  type vectors struct {
  68  	V2 struct {
  69  		Valid struct {
  70  			GetConversationKey []conversationKeyVector `json:"get_conversation_key"`
  71  			GetMessageKeys     struct {
  72  				ConversationKey string              `json:"conversation_key"`
  73  				Keys            []messageKeysVector `json:"keys"`
  74  			} `json:"get_message_keys"`
  75  			CalcPaddedLen         [][2]int               `json:"calc_padded_len"`
  76  			EncryptDecrypt        []encryptDecryptVector `json:"encrypt_decrypt"`
  77  			EncryptDecryptLongMsg []longMessageVector    `json:"encrypt_decrypt_long_msg"`
  78  		} `json:"valid"`
  79  		Invalid struct {
  80  			EncryptMsgLengths  []int                          `json:"encrypt_msg_lengths"`
  81  			GetConversationKey []invalidConversationKeyVector `json:"get_conversation_key"`
  82  			Decrypt            []invalidDecryptVector         `json:"decrypt"`
  83  		} `json:"invalid"`
  84  	} `json:"v2"`
  85  }
  86  
  87  func loadVectors(t *testing.T) vectors {
  88  	t.Helper()
  89  	data, err := os.ReadFile(vectorsPath)
  90  	if err != nil {
  91  		t.Fatalf("read vectors: %v", err)
  92  	}
  93  	sum := sha256.Sum256(data)
  94  	const wantSum = "269ed0f69e4c192512cc779e78c555090cebc7c785b609e338a62afc3ce25040"
  95  	if got := hex.EncodeToString(sum[:]); got != wantSum {
  96  		t.Fatalf("test vector checksum = %s, want %s", got, wantSum)
  97  	}
  98  	var v vectors
  99  	if err := json.Unmarshal(data, &v); err != nil {
 100  		t.Fatalf("parse vectors: %v", err)
 101  	}
 102  	return v
 103  }
 104  
 105  func mustHex(t *testing.T, s string, size int) []byte {
 106  	t.Helper()
 107  	b, err := hex.DecodeString(s)
 108  	if err != nil {
 109  		t.Fatalf("bad hex %q: %v", s, err)
 110  	}
 111  	if size >= 0 && len(b) != size {
 112  		t.Fatalf("hex %q has length %d, want %d", s, len(b), size)
 113  	}
 114  	return b
 115  }
 116  
 117  func bytes32(t *testing.T, s string) [32]byte {
 118  	t.Helper()
 119  	var out [32]byte
 120  	copy(out[:], mustHex(t, s, 32))
 121  	return out
 122  }
 123  
 124  func TestOfficialVectorChecksum(t *testing.T) {
 125  	// loadVectors verifies the checksum; this test makes the check appear in
 126  	// the verbose test list and guards against a stale file.
 127  	v := loadVectors(t)
 128  	if len(v.V2.Valid.EncryptDecrypt) == 0 {
 129  		t.Fatal("no encrypt_decrypt vectors")
 130  	}
 131  	if len(v.V2.Valid.EncryptDecrypt) < 10 || len(v.V2.Valid.GetConversationKey) < 35 {
 132  		t.Fatalf("unexpected vector counts: %d/%d", len(v.V2.Valid.EncryptDecrypt), len(v.V2.Valid.GetConversationKey))
 133  	}
 134  }
 135  
 136  func TestOfficialConversationKeys(t *testing.T) {
 137  	v := loadVectors(t)
 138  	for i, vec := range v.V2.Valid.GetConversationKey {
 139  		sec := bytes32(t, vec.Sec1)
 140  		pub := bytes32(t, vec.Pub2)
 141  		got, err := ConversationKey(sec, pub)
 142  		if err != nil {
 143  			t.Fatalf("vector %d (%s): unexpected error: %v", i, vec.ConversationKey, err)
 144  		}
 145  		want := bytes32(t, vec.ConversationKey)
 146  		if got != want {
 147  			t.Fatalf("vector %d: conversation key = %x, want %x", i, got, want)
 148  		}
 149  	}
 150  }
 151  
 152  func TestOfficialMessageKeys(t *testing.T) {
 153  	v := loadVectors(t)
 154  	conv := bytes32(t, v.V2.Valid.GetMessageKeys.ConversationKey)
 155  	if len(v.V2.Valid.GetMessageKeys.Keys) != 32 {
 156  		t.Fatalf("got %d message key vectors, want 32", len(v.V2.Valid.GetMessageKeys.Keys))
 157  	}
 158  	for i, vec := range v.V2.Valid.GetMessageKeys.Keys {
 159  		nonce := bytes32(t, vec.Nonce)
 160  		key, chachaNonce, hmacKey := messageKeys(conv, nonce)
 161  		if want := mustHex(t, vec.ChaChaKey, 32); !bytes.Equal(key[:], want) {
 162  			t.Fatalf("vector %d: chacha key = %x, want %x", i, key, want)
 163  		}
 164  		if want := mustHex(t, vec.ChaChaNonce, 12); !bytes.Equal(chachaNonce[:], want) {
 165  			t.Fatalf("vector %d: chacha nonce = %x, want %x", i, chachaNonce, want)
 166  		}
 167  		if want := mustHex(t, vec.HMACKey, 32); !bytes.Equal(hmacKey[:], want) {
 168  			t.Fatalf("vector %d: hmac key = %x, want %x", i, hmacKey, want)
 169  		}
 170  	}
 171  }
 172  
 173  func TestOfficialCalcPaddedLen(t *testing.T) {
 174  	v := loadVectors(t)
 175  	if len(v.V2.Valid.CalcPaddedLen) != 24 {
 176  		t.Fatalf("got %d padding vectors, want 24", len(v.V2.Valid.CalcPaddedLen))
 177  	}
 178  	for _, vec := range v.V2.Valid.CalcPaddedLen {
 179  		got := calcPaddedLen(uint64(vec[0]))
 180  		if got != uint64(vec[1]) {
 181  			t.Fatalf("calcPaddedLen(%d) = %d, want %d", vec[0], got, vec[1])
 182  		}
 183  	}
 184  }
 185  
 186  func TestOfficialEncryptDecrypt(t *testing.T) {
 187  	v := loadVectors(t)
 188  	for i, vec := range v.V2.Valid.EncryptDecrypt {
 189  		sec1 := bytes32(t, vec.Sec1)
 190  		sec2 := bytes32(t, vec.Sec2)
 191  		pub1, ok := secp256k1.PubKey(sec1)
 192  		if !ok {
 193  			t.Fatalf("vector %d: bad sec1", i)
 194  		}
 195  		pub2, ok := secp256k1.PubKey(sec2)
 196  		if !ok {
 197  			t.Fatalf("vector %d: bad sec2", i)
 198  		}
 199  		conv := bytes32(t, vec.ConversationKey)
 200  
 201  		convA, err := ConversationKey(sec1, pub2)
 202  		if err != nil {
 203  			t.Fatalf("vector %d: conv(1, pub2): %v", i, err)
 204  		}
 205  		if convA != conv {
 206  			t.Fatalf("vector %d: conv(1, pub2) = %x, want %x", i, convA, conv)
 207  		}
 208  		convB, err := ConversationKey(sec2, pub1)
 209  		if err != nil {
 210  			t.Fatalf("vector %d: conv(2, pub1): %v", i, err)
 211  		}
 212  		if convB != conv {
 213  			t.Fatalf("vector %d: conv(2, pub1) = %x, want %x", i, convB, conv)
 214  		}
 215  
 216  		nonce := bytes32(t, vec.Nonce)
 217  		payload, err := EncryptWithNonce(conv, nonce, []byte(vec.Plaintext))
 218  		if err != nil {
 219  			t.Fatalf("vector %d: encrypt: %v", i, err)
 220  		}
 221  		if payload != vec.Payload {
 222  			t.Fatalf("vector %d: payload = %s, want %s", i, payload, vec.Payload)
 223  		}
 224  		got, err := Decrypt(conv, vec.Payload)
 225  		if err != nil {
 226  			t.Fatalf("vector %d: decrypt: %v", i, err)
 227  		}
 228  		if string(got) != vec.Plaintext {
 229  			t.Fatalf("vector %d: plaintext = %q, want %q", i, got, vec.Plaintext)
 230  		}
 231  		// The other direction must decrypt with the same conversation key.
 232  		got, err = Decrypt(convB, vec.Payload)
 233  		if err != nil || string(got) != vec.Plaintext {
 234  			t.Fatalf("vector %d: reverse decrypt: %q, %v", i, got, err)
 235  		}
 236  	}
 237  }
 238  
 239  func TestOfficialLongMessages(t *testing.T) {
 240  	v := loadVectors(t)
 241  	if len(v.V2.Valid.EncryptDecryptLongMsg) != 3 {
 242  		t.Fatalf("got %d long message vectors, want 3", len(v.V2.Valid.EncryptDecryptLongMsg))
 243  	}
 244  	for i, vec := range v.V2.Valid.EncryptDecryptLongMsg {
 245  		conv := bytes32(t, vec.ConversationKey)
 246  		nonce := bytes32(t, vec.Nonce)
 247  		plaintext := []byte(strings.Repeat(vec.Pattern, vec.Repeat))
 248  		plainSum := sha256.Sum256(plaintext)
 249  		if got, want := hex.EncodeToString(plainSum[:]), vec.PlaintextSHA256; got != want {
 250  			t.Fatalf("vector %d: plaintext sha256 = %s, want %s", i, got, want)
 251  		}
 252  		payload, err := EncryptWithNonce(conv, nonce, plaintext)
 253  		if err != nil {
 254  			t.Fatalf("vector %d: encrypt: %v", i, err)
 255  		}
 256  		payloadSum := sha256.Sum256([]byte(payload))
 257  		if got, want := hex.EncodeToString(payloadSum[:]), vec.PayloadSHA256; got != want {
 258  			t.Fatalf("vector %d: payload sha256 = %s, want %s", i, got, want)
 259  		}
 260  		got, err := Decrypt(conv, payload)
 261  		if err != nil {
 262  			t.Fatalf("vector %d: decrypt: %v", i, err)
 263  		}
 264  		if !bytes.Equal(got, plaintext) {
 265  			t.Fatalf("vector %d: plaintext mismatch", i)
 266  		}
 267  	}
 268  }
 269  
 270  // extendedPrefixVectors are the extended length prefix checks from the NIP
 271  // itself (plaintext is "a" repeated plaintextLen times). Payloads are too
 272  // large to embed, so only SHA-256 checksums are published; paddedLen excludes
 273  // the length prefix.
 274  var extendedPrefixVectors = []struct {
 275  	plaintextLen    int
 276  	paddedLen       int
 277  	plaintextSHA256 string
 278  	payloadSHA256   string
 279  }{
 280  	{65535, 65536, "6e1bebca6a8229364a162a72ef064826c4cd7457bf54f190ef782bd9deff3e42", "6d8c2810d1e870fbaa1f0a0937126cca837a15f9260e27060c331d70a3c0bc84"},
 281  	{65536, 65536, "bf718b6f653bebc184e1479f1935b8da974d701b893afcf49e701f3e2f9f9c5a", "b7b4edb36ba92e267d322d56d9aebc22e7fa96ff52e3c12adc07f07a43cbc616"},
 282  	{65537, 81920, "008ffc88d3c96a9f307524eb361e47c5222a887fc45fa0c1fb8d429c5c23b430", "eeb7c7c5373894ea2c1547cfd3ccb15d5a0b2d619da852e5c79df792dcc9e435"},
 283  }
 284  
 285  func TestExtendedPrefixVectors(t *testing.T) {
 286  	const convHex = "c41c775356fd92eadc63ff5a0dc1da211b268cbea22316767095b2871ea1412d"
 287  	const nonceHex = "0000000000000000000000000000000000000000000000000000000000000001"
 288  	conv := bytes32(t, convHex)
 289  	nonce := bytes32(t, nonceHex)
 290  
 291  	for _, vec := range extendedPrefixVectors {
 292  		plaintext := bytes.Repeat([]byte{'a'}, vec.plaintextLen)
 293  		if got := hex.EncodeToString(sha256Sum(plaintext)); got != vec.plaintextSHA256 {
 294  			t.Fatalf("len %d: plaintext sha256 = %s, want %s", vec.plaintextLen, got, vec.plaintextSHA256)
 295  		}
 296  		if got, want := calcPaddedLen(uint64(vec.plaintextLen)), uint64(vec.paddedLen); got != want {
 297  			t.Fatalf("len %d: calcPaddedLen = %d, want %d", vec.plaintextLen, got, want)
 298  		}
 299  		payload, err := EncryptWithNonce(conv, nonce, plaintext)
 300  		if err != nil {
 301  			t.Fatalf("len %d: encrypt: %v", vec.plaintextLen, err)
 302  		}
 303  		if got := hex.EncodeToString(sha256Sum([]byte(payload))); got != vec.payloadSHA256 {
 304  			t.Fatalf("len %d: payload sha256 = %s, want %s", vec.plaintextLen, got, vec.payloadSHA256)
 305  		}
 306  		// Check the wire sizes documented in the NIP.
 307  		raw, err := base64.StdEncoding.DecodeString(payload)
 308  		if err != nil {
 309  			t.Fatalf("len %d: decode payload: %v", vec.plaintextLen, err)
 310  		}
 311  		prefixLen := 2
 312  		if vec.plaintextLen >= 65536 {
 313  			prefixLen = 6
 314  		}
 315  		if want := 1 + 32 + prefixLen + vec.paddedLen + 32; len(raw) != want {
 316  			t.Fatalf("len %d: raw payload = %d bytes, want %d", vec.plaintextLen, len(raw), want)
 317  		}
 318  		got, err := Decrypt(conv, payload)
 319  		if err != nil {
 320  			t.Fatalf("len %d: decrypt: %v", vec.plaintextLen, err)
 321  		}
 322  		if !bytes.Equal(got, plaintext) {
 323  			t.Fatalf("len %d: plaintext mismatch", vec.plaintextLen)
 324  		}
 325  	}
 326  }
 327  
 328  func TestOfficialInvalidConversationKeys(t *testing.T) {
 329  	v := loadVectors(t)
 330  	if len(v.V2.Invalid.GetConversationKey) != 8 {
 331  		t.Fatalf("got %d invalid conversation key vectors, want 8", len(v.V2.Invalid.GetConversationKey))
 332  	}
 333  	for i, vec := range v.V2.Invalid.GetConversationKey {
 334  		var sec, pub [32]byte
 335  		copy(sec[:], mustHex(t, vec.Sec1, 32))
 336  		copy(pub[:], mustHex(t, vec.Pub2, 32))
 337  		if _, err := ConversationKey(sec, pub); err == nil {
 338  			t.Fatalf("vector %d (%s): expected an error", i, vec.Note)
 339  		}
 340  	}
 341  }
 342  
 343  func TestOfficialInvalidDecrypts(t *testing.T) {
 344  	v := loadVectors(t)
 345  	if len(v.V2.Invalid.Decrypt) != 12 {
 346  		t.Fatalf("got %d invalid decrypt vectors, want 12", len(v.V2.Invalid.Decrypt))
 347  	}
 348  	for i, vec := range v.V2.Invalid.Decrypt {
 349  		conv := bytes32(t, vec.ConversationKey)
 350  		if _, err := Decrypt(conv, vec.Payload); err == nil {
 351  			t.Fatalf("vector %d (%s): expected an error", i, vec.Note)
 352  		}
 353  	}
 354  }
 355  
 356  // TestOfficialInvalidEncryptLengths covers invalid.encrypt_msg_lengths. The
 357  // published vector file predates the 6-byte extended length prefix and still
 358  // lists 65536, 100000 and 10000000, so only zero (an invalid empty plaintext)
 359  // is checked here; extended lengths are covered by the NIP's own checksums in
 360  // TestExtendedPrefixVectors.
 361  func TestOfficialInvalidEncryptLengths(t *testing.T) {
 362  	v := loadVectors(t)
 363  	if len(v.V2.Invalid.EncryptMsgLengths) == 0 {
 364  		t.Fatal("no invalid encrypt length vectors")
 365  	}
 366  	var conv [32]byte
 367  	conv[0] = 1
 368  	for _, n := range v.V2.Invalid.EncryptMsgLengths {
 369  		if n != 0 {
 370  			continue
 371  		}
 372  		if _, err := EncryptWithNonce(conv, [32]byte{}, make([]byte, n)); err == nil {
 373  			t.Fatalf("encrypting %d bytes: expected an error", n)
 374  		}
 375  	}
 376  }
 377  
 378  func sha256Sum(b []byte) []byte {
 379  	s := sha256.Sum256(b)
 380  	return s[:]
 381  }
 382  
 383  func TestConversationKeySymmetry(t *testing.T) {
 384  	pairs := [][2]string{
 385  		{"0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000002"},
 386  		{"5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a", "4b22aa260e4acb7021e32f38a6cdf4b673c6a277755bfce287e370c924dc936d"},
 387  		{"7a1c0b0f9b3f4e2d1c5a6b7c8d9e0f1a2b3c4d5e6f708192a3b4c5d6e7f80912", "112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00"},
 388  	}
 389  	for _, pair := range pairs {
 390  		secA := bytes32(t, pair[0])
 391  		secB := bytes32(t, pair[1])
 392  		pubA, ok := secp256k1.PubKey(secA)
 393  		if !ok {
 394  			t.Fatalf("bad secret a %s", pair[0])
 395  		}
 396  		pubB, ok := secp256k1.PubKey(secB)
 397  		if !ok {
 398  			t.Fatalf("bad secret b %s", pair[1])
 399  		}
 400  		k1, err := ConversationKey(secA, pubB)
 401  		if err != nil {
 402  			t.Fatal(err)
 403  		}
 404  		k2, err := ConversationKey(secB, pubA)
 405  		if err != nil {
 406  			t.Fatal(err)
 407  		}
 408  		if k1 != k2 {
 409  			t.Fatalf("conversation keys differ: %x %x", k1, k2)
 410  		}
 411  	}
 412  }
 413  
 414  func TestConversationKeyRejectsBadInput(t *testing.T) {
 415  	var zero, one [32]byte
 416  	one[31] = 1
 417  	if _, err := ConversationKey(zero, one); err == nil {
 418  		t.Fatal("accepted a zero secret key")
 419  	}
 420  	pubOne, ok := secp256k1.PubKey(one)
 421  	if !ok {
 422  		t.Fatal("pubkey")
 423  	}
 424  	offCurve := [32]byte{0xff}
 425  	if _, err := ConversationKey(one, offCurve); err == nil {
 426  		t.Fatal("accepted an off-curve public key")
 427  	}
 428  	if _, err := ConversationKey(one, pubOne); err != nil {
 429  		t.Fatalf("self ECDH: %v", err)
 430  	}
 431  }
 432  
 433  func TestRoundTrip(t *testing.T) {
 434  	conv := bytes32(t, "c41c775356fd92eadc63ff5a0dc1da211b268cbea22316767095b2871ea1412d")
 435  	lengths := []int{1, 31, 32, 33, 100, 65535, 65536, 70000}
 436  	for _, n := range lengths {
 437  		plaintext := bytes.Repeat([]byte{'z'}, n)
 438  		for i := range plaintext {
 439  			plaintext[i] = byte(i * 31)
 440  		}
 441  
 442  		// Deterministic nonce.
 443  		var nonce [32]byte
 444  		nonce[0] = byte(n)
 445  		nonce[1] = byte(n >> 8)
 446  		nonce[31] = 0x5a
 447  		payload, err := EncryptWithNonce(conv, nonce, plaintext)
 448  		if err != nil {
 449  			t.Fatalf("len %d: encrypt: %v", n, err)
 450  		}
 451  		got, err := Decrypt(conv, payload)
 452  		if err != nil {
 453  			t.Fatalf("len %d: decrypt: %v", n, err)
 454  		}
 455  		if !bytes.Equal(got, plaintext) {
 456  			t.Fatalf("len %d: round trip mismatch", n)
 457  		}
 458  
 459  		// Random nonce through Encrypt.
 460  		payload, err = Encrypt(conv, plaintext)
 461  		if err != nil {
 462  			t.Fatalf("len %d: Encrypt: %v", n, err)
 463  		}
 464  		got, err = Decrypt(conv, payload)
 465  		if err != nil {
 466  			t.Fatalf("len %d: Decrypt: %v", n, err)
 467  		}
 468  		if !bytes.Equal(got, plaintext) {
 469  			t.Fatalf("len %d: random nonce round trip mismatch", n)
 470  		}
 471  	}
 472  }
 473  
 474  func TestEncryptRejectsEmptyPlaintext(t *testing.T) {
 475  	var conv [32]byte
 476  	if _, err := Encrypt(conv, nil); err == nil {
 477  		t.Fatal("accepted nil plaintext")
 478  	}
 479  	if _, err := Encrypt(conv, []byte{}); err == nil {
 480  		t.Fatal("accepted empty plaintext")
 481  	}
 482  }
 483  
 484  // tamperDecode decodes a payload, applies mutate, and re-encodes it.
 485  func tamperDecode(t *testing.T, payload string, mutate func([]byte) []byte) string {
 486  	t.Helper()
 487  	raw, err := base64.StdEncoding.DecodeString(payload)
 488  	if err != nil {
 489  		t.Fatal(err)
 490  	}
 491  	return base64.StdEncoding.EncodeToString(mutate(raw))
 492  }
 493  
 494  func TestDecryptTamper(t *testing.T) {
 495  	conv := bytes32(t, "c41c775356fd92eadc63ff5a0dc1da211b268cbea22316767095b2871ea1412d")
 496  	var nonce [32]byte
 497  	nonce[0] = 7
 498  	plaintext := bytes.Repeat([]byte("tamper me "), 32) // 320 bytes
 499  	payload, err := EncryptWithNonce(conv, nonce, plaintext)
 500  	if err != nil {
 501  		t.Fatal(err)
 502  	}
 503  	if got, err := Decrypt(conv, payload); err != nil || !bytes.Equal(got, plaintext) {
 504  		t.Fatalf("baseline decrypt failed: %v", err)
 505  	}
 506  
 507  	tests := []struct {
 508  		name    string
 509  		payload string
 510  	}{
 511  		{"modified MAC", tamperDecode(t, payload, func(raw []byte) []byte {
 512  			raw[len(raw)-1] ^= 0x01
 513  			return raw
 514  		})},
 515  		{"modified ciphertext", tamperDecode(t, payload, func(raw []byte) []byte {
 516  			raw[50] ^= 0x80
 517  			return raw
 518  		})},
 519  		{"truncated payload", payload[:len(payload)-4]},
 520  		{"short payload", payload[:100]},
 521  		{"truncated base64 decode", payload[:len(payload)-20]},
 522  		{"hash prefix", "#" + payload[1:]},
 523  		{"empty", ""},
 524  		{"just hash", "#"},
 525  		{"invalid base64", payload[:len(payload)-1] + "!"},
 526  		{"bad version 0", tamperDecode(t, payload, func(raw []byte) []byte {
 527  			raw[0] = 0
 528  			return raw
 529  		})},
 530  		{"bad version 3", tamperDecode(t, payload, func(raw []byte) []byte {
 531  			raw[0] = 3
 532  			return raw
 533  		})},
 534  		{"bad version 1 with del", tamperDecode(t, payload, func(raw []byte) []byte {
 535  			raw[0] = 1
 536  			raw = append(raw[:1], raw[2:]...)
 537  			return raw
 538  		})},
 539  		{"wrong conversation key", ""},
 540  	}
 541  
 542  	for _, tc := range tests {
 543  		if tc.name == "wrong conversation key" {
 544  			wrong := conv
 545  			wrong[0] ^= 0xff
 546  			if _, err := Decrypt(wrong, payload); err == nil {
 547  				t.Fatal("wrong conversation key: expected an error")
 548  			}
 549  			continue
 550  		}
 551  		if _, err := Decrypt(conv, tc.payload); err == nil {
 552  			t.Fatalf("%s: expected an error", tc.name)
 553  		}
 554  	}
 555  }
 556  
 557  func TestUnpad(t *testing.T) {
 558  	// Valid small prefix.
 559  	small := append([]byte{0, 5}, []byte("hello")...)
 560  	small = append(small, make([]byte, 32-5)...)
 561  	got, err := unpad(small)
 562  	if err != nil || string(got) != "hello" {
 563  		t.Fatalf("unpad small = %q, %v", got, err)
 564  	}
 565  
 566  	// Valid extended prefix: 65536 bytes at exactly the padded length.
 567  	large := make([]byte, 6+65536)
 568  	large[3] = 1 // 0x00010000 big-endian in bytes 2..6
 569  	for i := 6; i < len(large); i++ {
 570  		large[i] = 0x41
 571  	}
 572  	got, err = unpad(large)
 573  	if err != nil || len(got) != 65536 || got[0] != 0x41 || got[65535] != 0x41 {
 574  		t.Fatalf("unpad large: len=%d err=%v", len(got), err)
 575  	}
 576  
 577  	bad := []struct {
 578  		name string
 579  		in   []byte
 580  	}{
 581  		{"empty", nil},
 582  		{"one byte", []byte{0}},
 583  		{"zero length", []byte{0, 0, 0, 0, 0, 0}},
 584  		{"short extended", []byte{0, 0, 0, 0}},
 585  		{"extended below threshold", []byte{0, 0, 0, 0, 1, 0, 'a'}},
 586  		{"length too small for buffer", []byte{0, 5, 'h', 'e', 'l', 'l', 'o'}},
 587  		{"padded length wrong", append(append([]byte{0, 5}, []byte("hello")...), make([]byte, 10)...)},
 588  		{"nonzero padding", append(append([]byte{0, 5}, []byte("hello")...), append([]byte{1}, make([]byte, 26)...)...)},
 589  		{"u16 zero not followed by u32", []byte{0, 0}},
 590  	}
 591  	for _, tc := range bad {
 592  		if _, err := unpad(tc.in); err == nil {
 593  			t.Fatalf("%s: expected an error", tc.name)
 594  		}
 595  	}
 596  }
 597