Skip to content

Commit d913de8

Browse files
authored
Merge pull request #601 from lochjin/dev1.2_seg
Optimize segwit taproot
2 parents d606bb4 + 0af831a commit d913de8

File tree

4 files changed

+326
-4
lines changed

4 files changed

+326
-4
lines changed

core/address/address.go

+34
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/Qitmeer/qng/crypto/ecc"
1111
"github.com/Qitmeer/qng/params"
1212
"golang.org/x/crypto/ripemd160"
13+
"strings"
1314
)
1415

1516
// encodeAddress returns a human-readable payment address given a ripemd160 hash
@@ -85,6 +86,39 @@ type ContractAddress struct {
8586
// DecodeAddress decodes the string encoding of an address and returns
8687
// the Address if addr is a valid encoding for a known address type
8788
func DecodeAddress(addr string) (types.Address, error) {
89+
oneIndex := strings.LastIndexByte(addr, '1')
90+
if oneIndex >= 1 {
91+
prefix := addr[:oneIndex+1]
92+
if params.IsBech32SegwitPrefix(prefix) {
93+
witnessVer, witnessProg, err := decodeSegWitAddress(addr)
94+
if err != nil {
95+
return nil, err
96+
}
97+
98+
// We currently only support P2WPKH and P2WSH, which is
99+
// witness version 0 and P2TR which is witness version
100+
// 1.
101+
if witnessVer != 0 && witnessVer != 1 {
102+
return nil, fmt.Errorf("unsupported witness version: %#x", byte(witnessVer))
103+
}
104+
105+
// The HRP is everything before the found '1'.
106+
hrp := prefix[:len(prefix)-1]
107+
108+
switch len(witnessProg) {
109+
case 20:
110+
return newAddressWitnessPubKeyHash(hrp, witnessProg)
111+
case 32:
112+
if witnessVer == 1 {
113+
return newAddressTaproot(hrp, witnessProg)
114+
}
115+
116+
return newAddressWitnessScriptHash(hrp, witnessProg)
117+
default:
118+
return nil, fmt.Errorf("unsupported witness program length: %d", len(witnessProg))
119+
}
120+
}
121+
}
88122
// Switch on decoded length to determine the type.
89123
decoded, netID, err := base58.QitmeerCheckDecode(addr)
90124
if err != nil {

core/address/segwitaddr_test.go

+245
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
package address
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"github.com/Qitmeer/qng/core/types"
7+
"github.com/Qitmeer/qng/params"
8+
"reflect"
9+
"strings"
10+
"testing"
11+
)
12+
13+
func TestAddresses(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
addr string
17+
encoded string
18+
valid bool
19+
result types.Address
20+
f func() (types.Address, error)
21+
net *params.Params
22+
}{
23+
// P2TR address tests.
24+
{
25+
name: "segwit v1 mainnet p2tr",
26+
addr: "m1paardr2nczq0rx5rqpfwnvpzm497zvux64y0f7wjgcs7xuuuh2nnqze6df7",
27+
encoded: "m1paardr2nczq0rx5rqpfwnvpzm497zvux64y0f7wjgcs7xuuuh2nnqze6df7",
28+
valid: true,
29+
result: TstAddressTaproot(
30+
1, [32]byte{
31+
0xef, 0x46, 0xd1, 0xaa, 0x78, 0x10, 0x1e, 0x33,
32+
0x50, 0x60, 0x0a, 0x5d, 0x36, 0x04, 0x5b, 0xa9,
33+
0x7c, 0x26, 0x70, 0xda, 0xa9, 0x1e, 0x9f, 0x3a,
34+
0x48, 0xc4, 0x3c, 0x6e, 0x73, 0x97, 0x54, 0xe6,
35+
}, params.MainNetParams.Bech32HRPSegwit,
36+
),
37+
f: func() (types.Address, error) {
38+
scriptHash := []byte{
39+
0xef, 0x46, 0xd1, 0xaa, 0x78, 0x10, 0x1e, 0x33,
40+
0x50, 0x60, 0x0a, 0x5d, 0x36, 0x04, 0x5b, 0xa9,
41+
0x7c, 0x26, 0x70, 0xda, 0xa9, 0x1e, 0x9f, 0x3a,
42+
0x48, 0xc4, 0x3c, 0x6e, 0x73, 0x97, 0x54, 0xe6,
43+
}
44+
return NewAddressTaproot(
45+
scriptHash, &params.MainNetParams,
46+
)
47+
},
48+
net: &params.MainNetParams,
49+
},
50+
51+
// Invalid bech32m tests. Source:
52+
{
53+
name: "segwit v1 invalid human-readable part",
54+
addr: "m1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut",
55+
valid: false,
56+
net: &params.MainNetParams,
57+
},
58+
{
59+
name: "segwit v1 mainnet bech32 instead of bech32m",
60+
addr: "m1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd",
61+
valid: false,
62+
net: &params.MainNetParams,
63+
},
64+
{
65+
name: "segwit v1 mainnet bech32 instead of bech32m upper case",
66+
addr: "m1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL",
67+
valid: false,
68+
net: &params.MainNetParams,
69+
},
70+
{
71+
name: "segwit v1 mainnet bech32m invalid character in checksum",
72+
addr: "m1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4",
73+
valid: false,
74+
net: &params.MainNetParams,
75+
},
76+
{
77+
name: "segwit mainnet witness v17",
78+
addr: "m130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R",
79+
valid: false,
80+
net: &params.MainNetParams,
81+
},
82+
{
83+
name: "segwit v1 mainnet bech32m invalid program length (1 byte)",
84+
addr: "m1pw5dgrnzv",
85+
valid: false,
86+
net: &params.MainNetParams,
87+
},
88+
{
89+
name: "segwit v1 mainnet bech32m invalid program length (41 bytes)",
90+
addr: "m1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav",
91+
valid: false,
92+
net: &params.MainNetParams,
93+
},
94+
{
95+
name: "segwit v1 mainnet bech32m zero padding of more than 4 bits",
96+
addr: "m1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf",
97+
valid: false,
98+
net: &params.MainNetParams,
99+
},
100+
{
101+
name: "segwit v1 mainnet bech32m empty data section",
102+
addr: "m1gmk9yu",
103+
valid: false,
104+
net: &params.MainNetParams,
105+
},
106+
107+
// Unsupported witness versions (version 0 and 1 only supported at this point)
108+
{
109+
name: "segwit mainnet witness v16",
110+
addr: "m1SW50QA3JX3S",
111+
valid: false,
112+
net: &params.MainNetParams,
113+
},
114+
{
115+
name: "segwit mainnet witness v2",
116+
addr: "m1zw508d6qejxtdg4y5r3zarvaryvg6kdaj",
117+
valid: false,
118+
net: &params.MainNetParams,
119+
},
120+
// Invalid segwit addresses
121+
{
122+
name: "segwit invalid checksum",
123+
addr: "m1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5",
124+
valid: false,
125+
net: &params.MainNetParams,
126+
},
127+
{
128+
name: "segwit invalid witness version",
129+
addr: "m13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2",
130+
valid: false,
131+
net: &params.MainNetParams,
132+
},
133+
{
134+
name: "segwit invalid program length",
135+
addr: "m1rw5uspcuh",
136+
valid: false,
137+
net: &params.MainNetParams,
138+
},
139+
{
140+
name: "segwit invalid program length",
141+
addr: "m10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90",
142+
valid: false,
143+
net: &params.MainNetParams,
144+
},
145+
{
146+
name: "segwit invalid program length for witness version 0 (per BIP141)",
147+
addr: "m1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
148+
valid: false,
149+
net: &params.MainNetParams,
150+
},
151+
}
152+
153+
for _, test := range tests {
154+
// Decode addr and compare error against valid.
155+
decoded, err := DecodeAddress(test.addr)
156+
if (err == nil) != test.valid {
157+
t.Errorf("%v: decoding test failed: %v", test.name, err)
158+
return
159+
}
160+
161+
if err == nil {
162+
// Ensure the stringer returns the same address as the
163+
// original.
164+
if decodedStringer, ok := decoded.(fmt.Stringer); ok {
165+
addr := test.addr
166+
167+
// For Segwit addresses the string representation
168+
// will always be lower case, so in that case we
169+
// convert the original to lower case first.
170+
if strings.Contains(test.name, "segwit") {
171+
addr = strings.ToLower(addr)
172+
}
173+
174+
if addr != decodedStringer.String() {
175+
t.Errorf("%v: String on decoded value does not match expected value: %v != %v",
176+
test.name, test.addr, decodedStringer.String())
177+
return
178+
}
179+
}
180+
181+
// Encode again and compare against the original.
182+
encoded := decoded.Encode()
183+
if test.encoded != encoded {
184+
t.Errorf("%v: decoding and encoding produced different addresses: %v != %v",
185+
test.name, test.encoded, encoded)
186+
return
187+
}
188+
189+
// Perform type-specific calculations.
190+
var saddr []byte
191+
if _, ok := decoded.(*AddressTaproot); ok {
192+
saddr = TstAddressTaprootSAddr(encoded)
193+
}
194+
// Check script address, as well as the Hash160 method for P2PKH and
195+
// P2SH addresses.
196+
if !bytes.Equal(saddr, decoded.Script()) {
197+
t.Errorf("%v: script addresses do not match:\n%x != \n%x",
198+
test.name, saddr, decoded.Script())
199+
return
200+
}
201+
// Ensure the address is for the expected network.
202+
if !decoded.IsForNetwork(test.net.Net) {
203+
t.Errorf("%v: calculated network does not match expected",
204+
test.name)
205+
return
206+
}
207+
} else {
208+
// If there is an error, make sure we can print it
209+
// correctly.
210+
errStr := err.Error()
211+
if errStr == "" {
212+
t.Errorf("%v: error was non-nil but message is"+
213+
"empty: %v", test.name, err)
214+
}
215+
}
216+
217+
if !test.valid {
218+
// If address is invalid, but a creation function exists,
219+
// verify that it returns a nil addr and non-nil error.
220+
if test.f != nil {
221+
_, err := test.f()
222+
if err == nil {
223+
t.Errorf("%v: address is invalid but creating new address succeeded",
224+
test.name)
225+
return
226+
}
227+
}
228+
continue
229+
}
230+
231+
// Valid test, compare address created with f against expected result.
232+
addr, err := test.f()
233+
if err != nil {
234+
t.Errorf("%v: address is valid but creating new address failed with error %v",
235+
test.name, err)
236+
return
237+
}
238+
239+
if !reflect.DeepEqual(addr, test.result) {
240+
t.Errorf("%v: created address does not match expected result",
241+
test.name)
242+
return
243+
}
244+
}
245+
}

core/address/utils.go

+30
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package address
44

55
import (
6+
"github.com/Qitmeer/qng/common/encode/bech32"
67
"github.com/Qitmeer/qng/core/types"
78
"github.com/Qitmeer/qng/log"
89
"github.com/Qitmeer/qng/params"
@@ -22,3 +23,32 @@ func IsForCurNetwork(addr string) bool {
2223
}
2324
return add.IsForNetwork(params.ActiveNetParams.Params.Net)
2425
}
26+
27+
// TstAddressTaproot creates an AddressTaproot, initiating the fields as given.
28+
func TstAddressTaproot(version byte, program [32]byte,
29+
hrp string) *AddressTaproot {
30+
31+
return &AddressTaproot{
32+
AddressSegWit{
33+
hrp: hrp,
34+
witnessVersion: version,
35+
witnessProgram: program[:],
36+
},
37+
}
38+
}
39+
40+
// TstAddressTaprootSAddr returns the expected witness program bytes for a
41+
// bech32m encoded P2TR bitcoin address.
42+
func TstAddressTaprootSAddr(addr string) []byte {
43+
_, data, err := bech32.Decode(addr)
44+
if err != nil {
45+
return []byte{}
46+
}
47+
48+
// First byte is version, rest is base 32 encoded data.
49+
data, err = bech32.ConvertBits(data[1:], 5, 8, false)
50+
if err != nil {
51+
return []byte{}
52+
}
53+
return data
54+
}

params/params.go

+17-4
Original file line numberDiff line numberDiff line change
@@ -306,10 +306,11 @@ var (
306306
)
307307

308308
var (
309-
registeredNets = make(map[protocol.Network]struct{})
310-
pubKeyHashAddrIDs = make(map[[2]byte]struct{})
311-
scriptHashAddrIDs = make(map[[2]byte]struct{})
312-
hdPrivToPubKeyIDs = make(map[[4]byte][]byte)
309+
registeredNets = make(map[protocol.Network]struct{})
310+
pubKeyHashAddrIDs = make(map[[2]byte]struct{})
311+
scriptHashAddrIDs = make(map[[2]byte]struct{})
312+
hdPrivToPubKeyIDs = make(map[[4]byte][]byte)
313+
bech32SegwitPrefixes = make(map[string]struct{})
313314
)
314315

315316
// Register registers the network parameters for a Bitcoin network. This may
@@ -330,6 +331,9 @@ func Register(params *Params) error {
330331
scriptHashAddrIDs[params.ScriptHashAddrID] = struct{}{}
331332
hdPrivToPubKeyIDs[params.HDPrivateKeyID] = params.HDPublicKeyID[:]
332333

334+
// A valid Bech32 encoded segwit address always has as prefix the
335+
// human-readable part for the given net followed by '1'.
336+
bech32SegwitPrefixes[params.Bech32HRPSegwit+"1"] = struct{}{}
333337
return nil
334338
}
335339

@@ -357,3 +361,12 @@ func hexMustDecode(hexStr string) []byte {
357361
}
358362
return b
359363
}
364+
365+
// IsBech32SegwitPrefix returns whether the prefix is a known prefix for segwit
366+
// addresses on any default or registered network. This is used when decoding
367+
// an address string into a specific address type.
368+
func IsBech32SegwitPrefix(prefix string) bool {
369+
prefix = strings.ToLower(prefix)
370+
_, ok := bech32SegwitPrefixes[prefix]
371+
return ok
372+
}

0 commit comments

Comments
 (0)