diff --git a/chain/bitcoin/address.go b/chain/bitcoin/address.go index 3228c374..8f875e90 100644 --- a/chain/bitcoin/address.go +++ b/chain/bitcoin/address.go @@ -38,6 +38,17 @@ func NewAddressEncoder(params *chaincfg.Params) AddressEncoder { // EncodeAddress implements the address.Encoder interface func (encoder AddressEncoder) EncodeAddress(rawAddr address.RawAddress) (address.Address, error) { + switch len(rawAddr) { + case 25: + return encoder.encodeBase58(rawAddr) + case 21, 33: + return encoder.encodeBech32(rawAddr) + default: + return address.Address(""), fmt.Errorf("non-exhaustive pattern: address length %v", len(rawAddr)) + } +} + +func (encoder AddressEncoder) encodeBase58(rawAddr address.RawAddress) (address.Address, error) { // Validate that the base58 address is in fact in correct format. encodedAddr := base58.Encode([]byte(rawAddr)) if _, err := btcutil.DecodeAddress(encodedAddr, encoder.params); err != nil { @@ -47,6 +58,25 @@ func (encoder AddressEncoder) EncodeAddress(rawAddr address.RawAddress) (address return address.Address(encodedAddr), nil } +func (encoder AddressEncoder) encodeBech32(rawAddr address.RawAddress) (address.Address, error) { + switch len(rawAddr) { + case 21: + addr, err := btcutil.NewAddressWitnessPubKeyHash(rawAddr[1:], encoder.params) + if err != nil { + return address.Address(""), fmt.Errorf("new address witness pubkey hash: %v", err) + } + return address.Address(addr.EncodeAddress()), nil + case 33: + addr, err := btcutil.NewAddressWitnessScriptHash(rawAddr[1:], encoder.params) + if err != nil { + return address.Address(""), fmt.Errorf("new address witness script hash: %v", err) + } + return address.Address(addr.EncodeAddress()), nil + default: + return address.Address(""), fmt.Errorf("non-exhaustive pattern: bech32 address length %v", len(rawAddr)) + } +} + // AddressDecoder encapsulates the chain specific configurations and implements // the address.Decoder interface type AddressDecoder struct { @@ -61,20 +91,21 @@ func NewAddressDecoder(params *chaincfg.Params) AddressDecoder { // DecodeAddress implements the address.Decoder interface func (decoder AddressDecoder) DecodeAddress(addr address.Address) (address.RawAddress, error) { - // Decode the checksummed base58 format address. - decoded, ver, err := base58.CheckDecode(string(addr)) + decodedAddr, err := btcutil.DecodeAddress(string(addr), decoder.params) if err != nil { - return nil, fmt.Errorf("checking: %v", err) - } - if len(decoded) != 20 { - return nil, fmt.Errorf("expected len 20, got len %v", len(decoded)) + return nil, fmt.Errorf("decode address: %v", err) } - // Validate the address format. - switch ver { - case decoder.params.PubKeyHashAddrID, decoder.params.ScriptHashAddrID: + switch a := decodedAddr.(type) { + case *btcutil.AddressPubKeyHash, *btcutil.AddressScriptHash: return address.RawAddress(base58.Decode(string(addr))), nil + case *btcutil.AddressWitnessPubKeyHash: + rawAddr := append([]byte{a.WitnessVersion()}, a.WitnessProgram()...) + return address.RawAddress(rawAddr), nil + case *btcutil.AddressWitnessScriptHash: + rawAddr := append([]byte{a.WitnessVersion()}, a.WitnessProgram()...) + return address.RawAddress(rawAddr), nil default: - return nil, fmt.Errorf("unexpected address prefix") + return nil, fmt.Errorf("non-exhaustive pattern: address %T", a) } } diff --git a/multichain_test.go b/multichain_test.go index 57f4c985..27f5165e 100644 --- a/multichain_test.go +++ b/multichain_test.go @@ -11,6 +11,7 @@ import ( "os/exec" "reflect" "strings" + "testing/quick" "time" "github.com/btcsuite/btcd/btcec" @@ -42,6 +43,9 @@ import ( ) var _ = Describe("Multichain", func() { + // new randomness + r := rand.New(rand.NewSource(time.Now().UnixNano())) + // Create context to work within. ctx := context.Background() @@ -98,8 +102,8 @@ var _ = Describe("Multichain", func() { }, func() multichain.Address { // Random bytes of script. - script := make([]byte, rand.Intn(100)) - rand.Read(script) + script := make([]byte, r.Intn(100)) + r.Read(script) // Create address script hash from the random script bytes. addrScriptHash, err := btcutil.NewAddressScriptHash(script, &chaincfg.RegressionNetParams) Expect(err).NotTo(HaveOccurred()) @@ -108,8 +112,8 @@ var _ = Describe("Multichain", func() { }, func() multichain.RawAddress { // Random bytes of script. - script := make([]byte, rand.Intn(100)) - rand.Read(script) + script := make([]byte, r.Intn(100)) + r.Read(script) // Create address script hash from the random script bytes. addrScriptHash, err := btcutil.NewAddressScriptHash(script, &chaincfg.RegressionNetParams) Expect(err).NotTo(HaveOccurred()) @@ -125,14 +129,14 @@ var _ = Describe("Multichain", func() { }, func() multichain.Address { pubKey := make([]byte, 64) - rand.Read(pubKey) + r.Read(pubKey) addr, err := filaddress.NewSecp256k1Address(pubKey) Expect(err).NotTo(HaveOccurred()) return multichain.Address(addr.String()) }, func() multichain.RawAddress { rawAddr := make([]byte, 20) - rand.Read(rawAddr) + r.Read(rawAddr) formattedRawAddr := append([]byte{byte(filaddress.SECP256K1)}, rawAddr[:]...) return multichain.RawAddress(pack.NewBytes(formattedRawAddr[:])) }, @@ -191,15 +195,15 @@ var _ = Describe("Multichain", func() { return multichain.RawAddress(pack.Bytes(addrBytes)) }, func() multichain.Address { - script := make([]byte, rand.Intn(100)) - rand.Read(script) + script := make([]byte, r.Intn(100)) + r.Read(script) addrScriptHash, err := bitcoincash.NewAddressScriptHash(script, &chaincfg.RegressionNetParams) Expect(err).NotTo(HaveOccurred()) return multichain.Address(addrScriptHash.EncodeAddress()) }, func() multichain.RawAddress { - script := make([]byte, rand.Intn(100)) - rand.Read(script) + script := make([]byte, r.Intn(100)) + r.Read(script) addrScriptHash, err := bitcoincash.NewAddressScriptHash(script, &chaincfg.RegressionNetParams) Expect(err).NotTo(HaveOccurred()) @@ -231,15 +235,15 @@ var _ = Describe("Multichain", func() { return multichain.RawAddress(pack.Bytes(base58.Decode(addrPubKeyHash.EncodeAddress()))) }, func() multichain.Address { - script := make([]byte, rand.Intn(100)) - rand.Read(script) + script := make([]byte, r.Intn(100)) + r.Read(script) addrScriptHash, err := zcash.NewAddressScriptHash(script, &zcash.RegressionNetParams) Expect(err).NotTo(HaveOccurred()) return multichain.Address(addrScriptHash.EncodeAddress()) }, func() multichain.RawAddress { - script := make([]byte, rand.Intn(100)) - rand.Read(script) + script := make([]byte, r.Intn(100)) + r.Read(script) addrScriptHash, err := zcash.NewAddressScriptHash(script, &zcash.RegressionNetParams) Expect(err).NotTo(HaveOccurred()) return multichain.RawAddress(pack.Bytes(base58.Decode(addrScriptHash.EncodeAddress()))) @@ -289,6 +293,54 @@ var _ = Describe("Multichain", func() { Expect(encodedAddr).To(Equal(scriptAddr)) }) } + + if chain.chain == multichain.Bitcoin { + mainnetEncodeDecoder := bitcoin.NewAddressEncodeDecoder(&chaincfg.MainNetParams) + + It("should decode a Bech32 address correctly", func() { + segwitAddrs := []string{ + "bc1qp3gcp95e85rupv9zgj57j0lvsqnzcehawzaax3", + "bc1qh6fjfx39ae4ahvusc4eggyrwjm65zyu83mzwlx", + "bc1q3zqxadsagdwjp2fpddn8dk5ge8lf0nn0p750ar", + "bc1q2lthuszmh0mynte4nzsfqtjjseu6fdrmeffr62", + "bc1qdqkfrt2hpgncqwut88809he6wxysfw8w3cgsh4", + "bc1qna5zwwuqcst3dqqx8rmwa66jpa45w28tlypg54", + "bc1qjk2ytl6uctuxfsyf8dn6ptwfsthfat4hd78l0m", + "bc1qyg6zhg9dhmkj0wz4svsdz6g0ujll225v0wc5hx", + "bc1quvtmmjccre6plqslujw7qcy820fycg2q2a73an", + "bc1qztxl2qc3k90uud846qfeawqzz3aedhq48vv3lu", + "bc1qvkknfkfhfr0axql478klvjs6sanwj6njym5wf2", + "bc1qya5t2pj7hqpezcnwh72k69h4cgg3srqwtd0e6w", + } + for _, segwitAddr := range segwitAddrs { + decodedRawAddr, err := mainnetEncodeDecoder.DecodeAddress(multichain.Address(segwitAddr)) + Expect(err).NotTo(HaveOccurred()) + encodedAddr, err := mainnetEncodeDecoder.EncodeAddress(decodedRawAddr) + Expect(err).NotTo(HaveOccurred()) + Expect(string(encodedAddr)).To(Equal(segwitAddr)) + } + }) + + It("should encode a Bech32 address correctly", func() { + loop := func() bool { + l := 21 + if r.Intn(2) == 1 { + l = 33 + } + randBytes := make([]byte, l) + r.Read(randBytes) + randBytes[0] = byte(0) + rawAddr := multichain.RawAddress(randBytes) + encodedAddr, err := mainnetEncodeDecoder.EncodeAddress(rawAddr) + Expect(err).NotTo(HaveOccurred()) + decodedRawAddr, err := mainnetEncodeDecoder.DecodeAddress(encodedAddr) + Expect(err).NotTo(HaveOccurred()) + Expect(decodedRawAddr).To(Equal(rawAddr)) + return true + } + Expect(quick.Check(loop, nil)).To(Succeed()) + }) + } }) } }) @@ -989,7 +1041,7 @@ var _ = Describe("Multichain", func() { It("should be able to fetch the latest block", func() { // get a random address randAddr := make([]byte, 20) - rand.Read(randAddr) + r.Read(randAddr) pkhAddr, err := utxoChain.newAddressPKH(randAddr) Expect(err).NotTo(HaveOccurred())