From ba75e609507b73c3e770685783f553e9b1a2225a Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Thu, 23 Sep 2021 00:29:28 +0200 Subject: [PATCH] Handle Bitcoin taproot addresses --- bchain/coins/btc/bitcoinlikeparser.go | 10 +- bchain/coins/btc/bitcoinparser_test.go | 160 +++++++++++++++++++++++++ bchain/coins/btc/bitcoinrpc.go | 6 +- bchain/coins/nuls/nulsparser.go | 4 +- bchain/coins/nuls/nulsparser_test.go | 13 +- go.mod | 2 +- go.sum | 10 +- 7 files changed, 185 insertions(+), 20 deletions(-) diff --git a/bchain/coins/btc/bitcoinlikeparser.go b/bchain/coins/btc/bitcoinlikeparser.go index 3143ead6..1577cea3 100644 --- a/bchain/coins/btc/bitcoinlikeparser.go +++ b/bchain/coins/btc/bitcoinlikeparser.go @@ -186,7 +186,7 @@ func (p *BitcoinLikeParser) outputScriptToAddresses(script []byte) ([]string, bo rv[i] = a.EncodeAddress() } var s bool - if sc == txscript.PubKeyHashTy || sc == txscript.WitnessV0PubKeyHashTy || sc == txscript.ScriptHashTy || sc == txscript.WitnessV0ScriptHashTy { + if sc == txscript.PubKeyHashTy || sc == txscript.WitnessV0PubKeyHashTy || sc == txscript.ScriptHashTy || sc == txscript.WitnessV0ScriptHashTy || sc == txscript.WitnessV1TaprootTy { s = true } else if len(rv) == 0 { or := p.TryParseOPReturn(script) @@ -345,13 +345,13 @@ func (p *BitcoinLikeParser) DeriveAddressDescriptors(xpub string, change uint32, if err != nil { return nil, err } - changeExtKey, err := extKey.Child(change) + changeExtKey, err := extKey.Derive(change) if err != nil { return nil, err } ad := make([]bchain.AddressDescriptor, len(indexes)) for i, index := range indexes { - indexExtKey, err := changeExtKey.Child(index) + indexExtKey, err := changeExtKey.Derive(index) if err != nil { return nil, err } @@ -372,13 +372,13 @@ func (p *BitcoinLikeParser) DeriveAddressDescriptorsFromTo(xpub string, change u if err != nil { return nil, err } - changeExtKey, err := extKey.Child(change) + changeExtKey, err := extKey.Derive(change) if err != nil { return nil, err } ad := make([]bchain.AddressDescriptor, toIndex-fromIndex) for index := fromIndex; index < toIndex; index++ { - indexExtKey, err := changeExtKey.Child(index) + indexExtKey, err := changeExtKey.Derive(index) if err != nil { return nil, err } diff --git a/bchain/coins/btc/bitcoinparser_test.go b/bchain/coins/btc/bitcoinparser_test.go index 72c251ab..b3c03bec 100644 --- a/bchain/coins/btc/bitcoinparser_test.go +++ b/bchain/coins/btc/bitcoinparser_test.go @@ -1,3 +1,4 @@ +//go:build unittest // +build unittest package btc @@ -59,6 +60,18 @@ func TestGetAddrDescFromAddress(t *testing.T) { want: "002003973a40ec94c0d10f6f6f0e7a62ba2044b7d19db6ff2bf60651e17fb29d8d29", wantErr: false, }, + { + name: " witness_unknown v1", + args: args{address: "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y"}, + want: "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6", + wantErr: false, + }, + { + name: " witness_unknown v16", + args: args{address: "bc1sw50qgdz25j"}, + want: "6002751e", + wantErr: false, + }, } parser := NewBitcoinParser(GetChainParams("main"), &Configuration{}) @@ -77,6 +90,64 @@ func TestGetAddrDescFromAddress(t *testing.T) { } } +func TestGetAddrDescFromAddressTestnet(t *testing.T) { + type args struct { + address string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "pubkeyhash", + args: args{address: "mtkbaiLiUH3fvGJeSzuN3kUgmJzqinLejJ"}, + want: "76a914912e2b234f941f30b18afbb4fa46171214bf66c888ac", + wantErr: false, + }, + { + name: "scripthash", + args: args{address: "2Mv28xcUJdFXBTfGMtja6fVBMCEbsH3r2AW"}, + want: "a9141e6ec5a1d12912b396d77d98dcb000e91f517fa487", + wantErr: false, + }, + { + name: "witness_v0_keyhash", + args: args{address: "tb1qupjdck20as3y4l95cd5wepkv0grcz0p7d8rd5s"}, + want: "0014e064dc594fec224afcb4c368ec86cc7a07813c3e", + wantErr: false, + }, + { + name: "witness_v0_scripthash", + args: args{address: "tb1qqwtn5s8vjnqdzrm0du885c46ypzt05vakmljhasx28shlv5a355seu0fjv"}, + want: "002003973a40ec94c0d10f6f6f0e7a62ba2044b7d19db6ff2bf60651e17fb29d8d29", + wantErr: false, + }, + { + name: "witness_v1_taproot", + args: args{address: "tb1pqsv2qyp8hsma46422ecfd3ek02jayumkkzjx7vkf3cqpmfd4ucpsx0cc9h"}, + want: "51200418a01027bc37daeaaa567096c7367aa5d27376b0a46f32c98e001da5b5e603", + wantErr: false, + }, + } + parser := NewBitcoinParser(GetChainParams("test"), &Configuration{}) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parser.GetAddrDescFromAddress(tt.args.address) + if (err != nil) != tt.wantErr { + t.Errorf("GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr) + return + } + h := hex.EncodeToString(got) + if !reflect.DeepEqual(h, tt.want) { + t.Errorf("GetAddrDescFromAddress() = %v, want %v", h, tt.want) + } + }) + } +} + func TestGetAddrDescFromVout(t *testing.T) { type args struct { vout bchain.Vout @@ -307,6 +378,95 @@ We know the game and we're gonna play it (TO FRONT) } } +func TestGetAddressesFromAddrDescTestnet(t *testing.T) { + type args struct { + script string + } + tests := []struct { + name string + args args + want []string + want2 bool + wantErr bool + }{ + { + name: "pubkeyhash", + args: args{script: "76a914912e2b234f941f30b18afbb4fa46171214bf66c888ac"}, + want: []string{"mtkbaiLiUH3fvGJeSzuN3kUgmJzqinLejJ"}, + want2: true, + wantErr: false, + }, + { + name: "pubkey compressed", + args: args{script: "2102a741071164b40b01c4ad28913c4aa2a1015cc5b064f0c802272552f17ae08750ac"}, + want: []string{"mkMe1fsfCWFext2qxf4bk3yiruBTvnici4"}, + want2: false, + wantErr: false, + }, + { + name: "pubkey uncompressed", + args: args{script: "41041057356b91bfd3efeff5fc0fa8b865faafafb67bd653c5da2cd16ce15c7b86db0e622c8e1e135f68918a23601eb49208c1ac72c7b64a4ee99c396cf788da16ccac"}, + want: []string{"mx43tNdg4JYY29ifrHjJpdbcCqqDGVSng5"}, + want2: false, + wantErr: false, + }, + { + name: "scripthash", + args: args{script: "a9141e6ec5a1d12912b396d77d98dcb000e91f517fa487"}, + want: []string{"2Mv28xcUJdFXBTfGMtja6fVBMCEbsH3r2AW"}, + want2: true, + wantErr: false, + }, + { + name: "witness_v0_keyhash", + args: args{script: "0014e064dc594fec224afcb4c368ec86cc7a07813c3e"}, + want: []string{"tb1qupjdck20as3y4l95cd5wepkv0grcz0p7d8rd5s"}, + want2: true, + wantErr: false, + }, + { + name: "witness_v0_scripthash", + args: args{script: "002003973a40ec94c0d10f6f6f0e7a62ba2044b7d19db6ff2bf60651e17fb29d8d29"}, + want: []string{"tb1qqwtn5s8vjnqdzrm0du885c46ypzt05vakmljhasx28shlv5a355seu0fjv"}, + want2: true, + wantErr: false, + }, + { + name: "witness_v1_taproot", + args: args{script: "51200418a01027bc37daeaaa567096c7367aa5d27376b0a46f32c98e001da5b5e603"}, + want: []string{"tb1pqsv2qyp8hsma46422ecfd3ek02jayumkkzjx7vkf3cqpmfd4ucpsx0cc9h"}, + want2: true, + wantErr: false, + }, + { + name: "OP_RETURN ascii", + args: args{script: "6a0461686f6a"}, + want: []string{"OP_RETURN (ahoj)"}, + want2: false, + wantErr: false, + }, + } + + parser := NewBitcoinParser(GetChainParams("test"), &Configuration{}) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b, _ := hex.DecodeString(tt.args.script) + got, got2, err := parser.GetAddressesFromAddrDesc(b) + if (err != nil) != tt.wantErr { + t.Errorf("TestGetAddressesFromAddrDesc_Testnet() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("TestGetAddressesFromAddrDesc_Testnet() = %v, want %v", got, tt.want) + } + if !reflect.DeepEqual(got2, tt.want2) { + t.Errorf("TestGetAddressesFromAddrDesc_Testnet() = %v, want %v", got2, tt.want2) + } + }) + } +} + var ( testTx1, testTx2, testTx3 bchain.Tx diff --git a/bchain/coins/btc/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go index 6aba5c45..50077f45 100644 --- a/bchain/coins/btc/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -678,10 +678,8 @@ func (b *BitcoinRPC) GetMempoolTransactions() ([]string, error) { // IsMissingTx return true if error means missing tx func IsMissingTx(err *bchain.RPCError) bool { - if err.Code == -5 { // "No such mempool or blockchain transaction" - return true - } - return false + // err.Code == -5 "No such mempool or blockchain transaction" + return err.Code == -5 } // GetTransactionForMempool returns a transaction by the transaction ID diff --git a/bchain/coins/nuls/nulsparser.go b/bchain/coins/nuls/nulsparser.go index 2ea1ab90..b8531e60 100644 --- a/bchain/coins/nuls/nulsparser.go +++ b/bchain/coins/nuls/nulsparser.go @@ -169,13 +169,13 @@ func (p *NulsParser) DeriveAddressDescriptorsFromTo(xpub string, change uint32, if err != nil { return nil, err } - changeExtKey, err := extKey.Child(change) + changeExtKey, err := extKey.Derive(change) if err != nil { return nil, err } ad := make([]bchain.AddressDescriptor, toIndex-fromIndex) for index := fromIndex; index < toIndex; index++ { - indexExtKey, err := changeExtKey.Child(index) + indexExtKey, err := changeExtKey.Derive(index) if err != nil { return nil, err } diff --git a/bchain/coins/nuls/nulsparser_test.go b/bchain/coins/nuls/nulsparser_test.go index 869318aa..15e90526 100644 --- a/bchain/coins/nuls/nulsparser_test.go +++ b/bchain/coins/nuls/nulsparser_test.go @@ -1,3 +1,4 @@ +//go:build unittest // +build unittest package nuls @@ -359,13 +360,13 @@ func TestDeriveAddressDescriptorsFromTo(t *testing.T) { t.Errorf("DeriveAddressDescriptorsFromTo() error = %v", err) return } - changeExtKey, err := extKey.Child(0) + changeExtKey, err := extKey.Derive(0) if err != nil { t.Errorf("DeriveAddressDescriptorsFromTo() error = %v", err) return } - key1, _ := changeExtKey.Child(0) + key1, _ := changeExtKey.Derive(0) priKey1, _ := key1.ECPrivKey() wantPriKey1 := "0x995c98115809359eb57a5e179558faddd55ef88f88e5cf58617a5f9f3d6bb3a1" if !reflect.DeepEqual(hexutil.MustDecode(wantPriKey1), priKey1.Serialize()) { @@ -379,7 +380,7 @@ func TestDeriveAddressDescriptorsFromTo(t *testing.T) { return } - key2, _ := changeExtKey.Child(1) + key2, _ := changeExtKey.Derive(1) priKey2, _ := key2.ECPrivKey() wantPriKey2 := "0x0f65dee42d3c974c1a4bcc79f141be89715dc8d6406faa9ad4f1f55ca95fabc8" if !reflect.DeepEqual(hexutil.MustDecode(wantPriKey2), priKey2.Serialize()) { @@ -393,7 +394,7 @@ func TestDeriveAddressDescriptorsFromTo(t *testing.T) { return } - key3, _ := changeExtKey.Child(2) + key3, _ := changeExtKey.Derive(2) priKey3, _ := key3.ECPrivKey() wantPriKey3 := "0x6fd98d1d9c3f3ac1ff61bbf3f20e89f00ffa8d43a554f2a7d73fd464b6666f45" if !reflect.DeepEqual(hexutil.MustDecode(wantPriKey3), priKey3.Serialize()) { @@ -407,7 +408,7 @@ func TestDeriveAddressDescriptorsFromTo(t *testing.T) { return } - key4, _ := changeExtKey.Child(3) + key4, _ := changeExtKey.Derive(3) priKey4, _ := key4.ECPrivKey() wantPriKey4 := "0x21412d9e33aba493faf4bc7d408ed5290bea5b36a7beec554b858051f8d4bff3" if !reflect.DeepEqual(hexutil.MustDecode(wantPriKey4), priKey4.Serialize()) { @@ -421,7 +422,7 @@ func TestDeriveAddressDescriptorsFromTo(t *testing.T) { return } - key5, _ := changeExtKey.Child(4) + key5, _ := changeExtKey.Derive(4) priKey5, _ := key5.ECPrivKey() wantPriKey5 := "0xdc3d290e32a4e0f38bc26c25a78ceb1c8779110883d9cb0be54629043c1f8724" if !reflect.DeepEqual(hexutil.MustDecode(wantPriKey5), priKey5.Serialize()) { diff --git a/go.mod b/go.mod index 9e71e1c9..2cc6b4fb 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b // indirect github.com/martinboehm/bchutil v0.0.0-20190104112650-6373f11b6efe github.com/martinboehm/btcd v0.0.0-20200313230603-83af86142d93 - github.com/martinboehm/btcutil v0.0.0-20200229134221-d7706467ae8f + github.com/martinboehm/btcutil v0.0.0-20210922221517-e83b0c752949 github.com/martinboehm/golang-socketio v0.0.0-20180414165752-f60b0a8befde github.com/mr-tron/base58 v1.2.0 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect diff --git a/go.sum b/go.sum index c0f10da7..6dff804d 100644 --- a/go.sum +++ b/go.sum @@ -378,6 +378,7 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kkdai/bstream v0.0.0-20171226095907-f71540b9dfdc/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= @@ -401,11 +402,16 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/martinboehm/bchutil v0.0.0-20190104112650-6373f11b6efe h1:khZWpHuxJNh2EGzBbaS6EQ2d6KxgK31WeG0TnlTMUD4= github.com/martinboehm/bchutil v0.0.0-20190104112650-6373f11b6efe/go.mod h1:0hw4tpGU+9slqN/DrevhjTMb0iR9esxzpCdx8I6/UzU= +github.com/martinboehm/btcd v0.0.0-20190104121910-8e7c0427fee5/go.mod h1:rKQj/jGwFruYjpM6vN+syReFoR0DsLQaajhyH/5mwUE= github.com/martinboehm/btcd v0.0.0-20200313230603-83af86142d93 h1:SBXxF9UMEPAswVhAt3Y275Lx59Do8C/rpAmg6k73HYY= github.com/martinboehm/btcd v0.0.0-20200313230603-83af86142d93/go.mod h1:rKQj/jGwFruYjpM6vN+syReFoR0DsLQaajhyH/5mwUE= github.com/martinboehm/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:NIviPmxe43yBgIB4HGB4w4kv9/s5kaDa/pi+wZAAxQo= -github.com/martinboehm/btcutil v0.0.0-20200229134221-d7706467ae8f h1:MMI9XvVjNHkqQDDyud0Ll0qd/w4jAhgkLZMY8q6KbR8= -github.com/martinboehm/btcutil v0.0.0-20200229134221-d7706467ae8f/go.mod h1:NIviPmxe43yBgIB4HGB4w4kv9/s5kaDa/pi+wZAAxQo= +github.com/martinboehm/btcutil v0.0.0-20210914231321-8ece5dcd9f5f h1:zDEDlafs4y1CjDqcowXFzcttj/wt6N2wV9U2NihjgcU= +github.com/martinboehm/btcutil v0.0.0-20210914231321-8ece5dcd9f5f/go.mod h1:8iJaVY/VHW6lnojpTXf5X4gF2dx81Xtj2R6lJp2colA= +github.com/martinboehm/btcutil v0.0.0-20210922133746-0042eb304b5b h1:xm/0cKQ6PffWGXfPyfanJaZ7skZo/6uT6pb+mtVEU1o= +github.com/martinboehm/btcutil v0.0.0-20210922133746-0042eb304b5b/go.mod h1:8iJaVY/VHW6lnojpTXf5X4gF2dx81Xtj2R6lJp2colA= +github.com/martinboehm/btcutil v0.0.0-20210922221517-e83b0c752949 h1:GdmOPmno0QNl9jVMJZOTj7ekgGA9zRG6gn1X9jutrkU= +github.com/martinboehm/btcutil v0.0.0-20210922221517-e83b0c752949/go.mod h1:8iJaVY/VHW6lnojpTXf5X4gF2dx81Xtj2R6lJp2colA= github.com/martinboehm/golang-socketio v0.0.0-20180414165752-f60b0a8befde h1:Tz7WkXgQjeQVymqSQkEapbe/ZuzKCvb6GANFHnl0uAE= github.com/martinboehm/golang-socketio v0.0.0-20180414165752-f60b0a8befde/go.mod h1:p35TWcm7GkAwvPcUCEq4H+yTm0gA8Aq7UvGnbK6olQk= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=