Skip to content

Commit

Permalink
Merge pull request #68 from renproject/feat/segwit-addr
Browse files Browse the repository at this point in the history
support segwit addresses for bitcoin
  • Loading branch information
loongy authored Dec 23, 2020
2 parents e35ce8d + 8ee714d commit cd69006
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 25 deletions.
51 changes: 41 additions & 10 deletions chain/bitcoin/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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)
}
}
82 changes: 67 additions & 15 deletions multichain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os/exec"
"reflect"
"strings"
"testing/quick"
"time"

"github.com/btcsuite/btcd/btcec"
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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())
Expand All @@ -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())
Expand All @@ -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[:]))
},
Expand Down Expand Up @@ -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())

Expand Down Expand Up @@ -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())))
Expand Down Expand Up @@ -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())
})
}
})
}
})
Expand Down Expand Up @@ -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())

Expand Down

0 comments on commit cd69006

Please sign in to comment.