package bech32 import ( "encoding/hex" "strings" "testing" ) // TestBIP173Vectors: the valid addresses from the BIP173 test vectors. // // The first is the canonical example: a 20-byte program of 0x751e... encoded for the "bc" // prefix. If the checksum or the 5-bit regrouping were wrong, this would not match. func TestBIP173Vectors(t *testing.T) { valid := map[string]string{ "751e76e8199196d454941c45d1b3a323f1433bd6": "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262": "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", } for program, want := range valid { raw, err := hex.DecodeString(program) if err != nil { t.Fatal(err) } got, err := EncodeSegwit("bc", 0, raw) if err != nil { t.Fatalf("%s: %v", program, err) } if got != want { t.Fatalf("program %s encoded to %q, want %q", program, got, want) } hrp, version, decoded, err := DecodeSegwit(want) if err != nil { t.Fatalf("%s: %v", want, err) } if hrp != "bc" || version != 0 || hex.EncodeToString(decoded) != program { t.Fatalf("%s decoded to %q/%d/%s", want, hrp, version, hex.EncodeToString(decoded)) } } } // TestRegtestPrefix: the chain profile's prefix is part of the address, so the same // program under bcrt is a different string. func TestRegtestPrefix(t *testing.T) { raw, err := hex.DecodeString("751e76e8199196d454941c45d1b3a323f1433bd6") if err != nil { t.Fatal(err) } mainnet, err := EncodeSegwit("bc", 0, raw) if err != nil { t.Fatal(err) } regtest, err := EncodeSegwit("bcrt", 0, raw) if err != nil { t.Fatal(err) } if mainnet == regtest { t.Fatal("the same program encoded identically under two prefixes") } if !strings.HasPrefix(regtest, "bcrt1") { t.Fatalf("the regtest address is %q", regtest) } // The program survives both, which is what makes the prefix a profile fact and not a // second encoding. for _, address := range []string{mainnet, regtest} { hrp, _, decoded, err := DecodeSegwit(address) if err != nil { t.Fatalf("%s: %v", address, err) } if len(hrp) == 0 || hex.EncodeToString(decoded) != hex.EncodeToString(raw) { t.Fatalf("%s decoded to %q/%s", address, hrp, hex.EncodeToString(decoded)) } } } // TestRefusals: the shapes that must not be accepted, each for its own reason. func TestRefusals(t *testing.T) { raw, err := hex.DecodeString("751e76e8199196d454941c45d1b3a323f1433bd6") if err != nil { t.Fatal(err) } good, err := EncodeSegwit("bc", 0, raw) if err != nil { t.Fatal(err) } // One character changed is a different checksum. broken := []byte(good) broken[len(broken)-1] ^= 1 cases := map[string]string{ "a changed character": string(broken), "mixed case": strings.ToUpper(good[:4]) + good[4:], "no separator": "bcqw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", "too short": "bc1qqqqq", "a bad character": good[:len(good)-1] + "b", } for name, address := range cases { if _, _, _, err := DecodeSegwit(address); err == nil { t.Errorf("%s was accepted: %q", name, address) } } if _, err := EncodeSegwit("bc", 1, raw); err == nil { t.Fatal("witness version 1 was encoded with the version 0 checksum") } if _, err := EncodeSegwit("bc", 0, raw[:19]); err == nil { t.Fatal("a 19-byte program was encoded") } if _, err := EncodeSegwit("", 0, raw); err == nil { t.Fatal("an empty prefix was encoded") } if _, err := EncodeSegwit("BC", 0, raw); err == nil { t.Fatal("an uppercase prefix was encoded") } } // TestRoundTrip: every program length a wallet uses survives encode and decode. func TestRoundTrip(t *testing.T) { for _, size := range []int{20, 32} { program := make([]byte, size) for i := range program { program[i] = byte(i*7 + 1) } for _, hrp := range []string{"bc", "tb", "bcrt"} { address, err := EncodeSegwit(hrp, 0, program) if err != nil { t.Fatalf("%s/%d: %v", hrp, size, err) } gotHRP, _, decoded, err := DecodeSegwit(address) if err != nil { t.Fatalf("%s: %v", address, err) } if gotHRP != hrp || hex.EncodeToString(decoded) != hex.EncodeToString(program) { t.Fatalf("%s decoded to %q/%s", address, gotHRP, hex.EncodeToString(decoded)) } } } }