decred.org/dcrdex@v1.0.3/dex/networks/btc/descriptors_test.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package btc
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/hex"
     9  	"reflect"
    10  	"testing"
    11  
    12  	"github.com/btcsuite/btcd/btcec/v2"
    13  	"github.com/btcsuite/btcd/btcutil"
    14  	"github.com/btcsuite/btcd/chaincfg"
    15  	"github.com/davecgh/go-spew/spew"
    16  )
    17  
    18  var chainParams = &chaincfg.RegressionNetParams
    19  
    20  func TestParseDescriptor(t *testing.T) {
    21  	tests := []struct {
    22  		name    string
    23  		desc    string
    24  		want    *Descriptor
    25  		wantErr bool
    26  	}{
    27  		{
    28  			"wpkh with bare key",
    29  			"wpkh([8a94b43c]039e9e0813e46041e2fddf46640006f4e9ae5d4d6ab811d0d2a6b372d0b136ba8a)#eq3vqyes",
    30  			&Descriptor{
    31  				Function: "wpkh",
    32  				KeyOrigin: &KeyOrigin{
    33  					Fingerprint: "8a94b43c", // fingerprint of corresponding private key itself
    34  					Path:        "",
    35  					Steps:       nil,
    36  				},
    37  				Key:        "039e9e0813e46041e2fddf46640006f4e9ae5d4d6ab811d0d2a6b372d0b136ba8a",
    38  				KeyFmt:     KeyHexPub,
    39  				Nested:     nil,
    40  				Expression: "[8a94b43c]039e9e0813e46041e2fddf46640006f4e9ae5d4d6ab811d0d2a6b372d0b136ba8a",
    41  				Checksum:   "eq3vqyes",
    42  			},
    43  			false,
    44  		}, {
    45  			"wpkh with origin and checksum",
    46  			"wpkh([b940190e/84'/1'/0'/0/0]030003429cd5d23b1a229ec88dba6f2b69fb539fe26cd80229267aa0c992dc26b2)#0pfw7rck",
    47  			&Descriptor{
    48  				Function: "wpkh",
    49  				KeyOrigin: &KeyOrigin{
    50  					Fingerprint: "b940190e",
    51  					Path:        "84'/1'/0'/0/0",
    52  					Steps:       []uint32{2147483732, 2147483649, 2147483648, 0, 0},
    53  				},
    54  				Key:        "030003429cd5d23b1a229ec88dba6f2b69fb539fe26cd80229267aa0c992dc26b2",
    55  				KeyFmt:     KeyHexPub,
    56  				Nested:     nil,
    57  				Expression: "[b940190e/84'/1'/0'/0/0]030003429cd5d23b1a229ec88dba6f2b69fb539fe26cd80229267aa0c992dc26b2",
    58  				Checksum:   "0pfw7rck",
    59  			},
    60  			false,
    61  		}, {
    62  			"range/extended wpkh-in-sh with origin and checksum",
    63  			"sh(wpkh([b940190e/49'/1'/0']tpubDCDYiBwbWWM3FRB55DcdgWyr7AVraCmXSgnVHZpyJ716tWigvdhShXGgAREnQwXjBqvnuaT7k1oHA5LD2HN5uPjp1u4ubAemppGmqioFHAq/1/*))#a73wy5hk",
    64  			&Descriptor{
    65  				Function: "sh",
    66  				KeyOrigin: &KeyOrigin{ // dup of nested
    67  					Fingerprint: "b940190e",
    68  					Path:        "49'/1'/0'",
    69  					Steps:       []uint32{2147483697, 2147483649, 2147483648},
    70  				},
    71  				Key:    "tpubDCDYiBwbWWM3FRB55DcdgWyr7AVraCmXSgnVHZpyJ716tWigvdhShXGgAREnQwXjBqvnuaT7k1oHA5LD2HN5uPjp1u4ubAemppGmqioFHAq/1/*", // dup of nested
    72  				KeyFmt: KeyExtended,                                                                                                           // dup of nested
    73  				Nested: &Descriptor{
    74  					Function: "wpkh",
    75  					KeyOrigin: &KeyOrigin{
    76  						Fingerprint: "b940190e",
    77  						Path:        "49'/1'/0'",
    78  						Steps:       []uint32{2147483697, 2147483649, 2147483648},
    79  					},
    80  					Key:        "tpubDCDYiBwbWWM3FRB55DcdgWyr7AVraCmXSgnVHZpyJ716tWigvdhShXGgAREnQwXjBqvnuaT7k1oHA5LD2HN5uPjp1u4ubAemppGmqioFHAq/1/*",
    81  					KeyFmt:     KeyExtended,
    82  					Nested:     nil,
    83  					Expression: "[b940190e/49'/1'/0']tpubDCDYiBwbWWM3FRB55DcdgWyr7AVraCmXSgnVHZpyJ716tWigvdhShXGgAREnQwXjBqvnuaT7k1oHA5LD2HN5uPjp1u4ubAemppGmqioFHAq/1/*",
    84  					Checksum:   "",
    85  				},
    86  				Expression: "wpkh([b940190e/49'/1'/0']tpubDCDYiBwbWWM3FRB55DcdgWyr7AVraCmXSgnVHZpyJ716tWigvdhShXGgAREnQwXjBqvnuaT7k1oHA5LD2HN5uPjp1u4ubAemppGmqioFHAq/1/*)",
    87  				Checksum:   "a73wy5hk",
    88  			},
    89  			false,
    90  		}, {
    91  			"range private wpkh master (no key origin)",
    92  			"wpkh(tprv8ZgxMBicQKsPdAQ2QZeTReB2hH2aKXBWGqgnrW1aYbutbC7YfUtPPJm1Nppb6eXy5hnLRrRqwCctBecfZV8HLNsLeivVhKT1BYFBiRbhUES/84'/1'/0'/0/*)#pm06dltl",
    93  			&Descriptor{
    94  				Function:   "wpkh",
    95  				KeyOrigin:  nil,
    96  				Key:        "tprv8ZgxMBicQKsPdAQ2QZeTReB2hH2aKXBWGqgnrW1aYbutbC7YfUtPPJm1Nppb6eXy5hnLRrRqwCctBecfZV8HLNsLeivVhKT1BYFBiRbhUES/84'/1'/0'/0/*",
    97  				KeyFmt:     KeyExtended,
    98  				Nested:     nil,
    99  				Expression: "tprv8ZgxMBicQKsPdAQ2QZeTReB2hH2aKXBWGqgnrW1aYbutbC7YfUtPPJm1Nppb6eXy5hnLRrRqwCctBecfZV8HLNsLeivVhKT1BYFBiRbhUES/84'/1'/0'/0/*",
   100  				Checksum:   "pm06dltl",
   101  			},
   102  			false,
   103  		}, {
   104  			"invalid nested sh",
   105  			"wsh(sh(abababab))",
   106  			nil,
   107  			true,
   108  		}, {
   109  			"invalid nested wsh",
   110  			"wsh(wsh(abababab))",
   111  			nil,
   112  			true,
   113  		},
   114  		{
   115  			"valid nested wsh",
   116  			"sh(wsh(pk(03a40e1db1a51231027a8261b15317a59218b6c1c5dbd3d155688a3fb32c547ccc)))",
   117  			&Descriptor{
   118  				Function:  "sh",
   119  				KeyOrigin: nil,
   120  				Key:       "03a40e1db1a51231027a8261b15317a59218b6c1c5dbd3d155688a3fb32c547ccc", // dup of nested
   121  				KeyFmt:    KeyHexPub,                                                            // dup of nested
   122  				Nested: &Descriptor{
   123  					Function:  "wsh",
   124  					KeyOrigin: nil,
   125  					Key:       "03a40e1db1a51231027a8261b15317a59218b6c1c5dbd3d155688a3fb32c547ccc",
   126  					KeyFmt:    KeyHexPub,
   127  					Nested: &Descriptor{
   128  						Function:   "pk",
   129  						KeyOrigin:  nil,
   130  						Key:        "03a40e1db1a51231027a8261b15317a59218b6c1c5dbd3d155688a3fb32c547ccc",
   131  						KeyFmt:     KeyHexPub,
   132  						Nested:     nil,
   133  						Expression: "03a40e1db1a51231027a8261b15317a59218b6c1c5dbd3d155688a3fb32c547ccc",
   134  						Checksum:   "",
   135  					},
   136  					Expression: "pk(03a40e1db1a51231027a8261b15317a59218b6c1c5dbd3d155688a3fb32c547ccc)",
   137  					Checksum:   "",
   138  				},
   139  				Expression: "wsh(pk(03a40e1db1a51231027a8261b15317a59218b6c1c5dbd3d155688a3fb32c547ccc))",
   140  				Checksum:   "",
   141  			},
   142  			false,
   143  		},
   144  	}
   145  	for _, tt := range tests {
   146  		t.Run(tt.name, func(t *testing.T) {
   147  			got, err := ParseDescriptor(tt.desc)
   148  			if (err != nil) != tt.wantErr {
   149  				t.Errorf("ParseDescriptor() error = %v, wantErr %v", err, tt.wantErr)
   150  				return
   151  			}
   152  
   153  			if !reflect.DeepEqual(got, tt.want) {
   154  				t.Errorf("ParseDescriptor() = %#v, want %#v", got, tt.want)
   155  				spew.Dump(got)
   156  			}
   157  		})
   158  	}
   159  }
   160  
   161  func TestDescriptors(t *testing.T) {
   162  	// getaddressinfo bcrt1qwuqqg9dajf7f7ddxp4un3p2dtagvrny4qll6xe
   163  	descAddr := "wpkh([b940190e/84'/1'/0'/0/0]030003429cd5d23b1a229ec88dba6f2b69fb539fe26cd80229267aa0c992dc26b2)#0pfw7rck"
   164  	d, err := ParseDescriptor(descAddr)
   165  	if err != nil {
   166  		t.Fatal(err)
   167  	}
   168  	if d.KeyFmt != KeyHexPub {
   169  		t.Fatalf("wrong key type: %v", d.Key)
   170  	}
   171  
   172  	if d.KeyOrigin == nil {
   173  		t.Fatalf("no key origin section")
   174  	}
   175  	if d.KeyOrigin.Fingerprint != "b940190e" {
   176  		t.Fatalf("incorrect key origin master fingerprint: %v", d.KeyOrigin.Fingerprint)
   177  	}
   178  	if d.KeyOrigin.Path != "84'/1'/0'/0/0" {
   179  		t.Fatalf("wrong path %q", d.KeyOrigin.Path)
   180  	}
   181  	if len(d.KeyOrigin.Steps) != 5 {
   182  		t.Fatalf("wrong number of steps in path: %d", len(d.KeyOrigin.Steps))
   183  	}
   184  	addrPath := d.KeyOrigin.Steps
   185  	addrOriginFP := d.KeyOrigin.Fingerprint
   186  
   187  	addrPubKey, err := hex.DecodeString(d.Key)
   188  	if err != nil {
   189  		t.Fatalf("DecodeString: %v", err)
   190  	}
   191  	if _, err = btcec.ParsePubKey(addrPubKey); err != nil {
   192  		t.Fatalf("ParsePubKey: %v", err)
   193  	}
   194  	addr, err := btcutil.NewAddressWitnessPubKeyHash(btcutil.Hash160(addrPubKey), chainParams)
   195  	if err != nil {
   196  		t.Fatalf("NewAddressWitnessPubKeyHash: %v", err)
   197  	}
   198  	if addr.String() != "bcrt1qwuqqg9dajf7f7ddxp4un3p2dtagvrny4qll6xe" {
   199  		t.Fatal("wrong address pubkey")
   200  	}
   201  
   202  	// listdescriptors private=true
   203  
   204  	descPriv := "wpkh(tprv8ZgxMBicQKsPdAQ2QZeTReB2hH2aKXBWGqgnrW1aYbutbC7YfUtPPJm1Nppb6eXy5hnLRrRqwCctBecfZV8HLNsLeivVhKT1BYFBiRbhUES/84'/1'/0'/0/*)#pm06dltl"
   205  	dPriv, err := ParseDescriptor(descPriv)
   206  	if err != nil {
   207  		t.Fatal(err)
   208  	}
   209  	if dPriv.KeyFmt != KeyExtended {
   210  		t.Fatalf("wrong key type: %v", dPriv.Key)
   211  	}
   212  	if dPriv.KeyOrigin != nil {
   213  		t.Fatalf("unexpected key origin section")
   214  	}
   215  
   216  	xPriv, fingerprint, pathStr, isRange, err := ParseKeyExtended(dPriv.Key)
   217  	if err != nil {
   218  		t.Fatal(err)
   219  	}
   220  	if !xPriv.IsPrivate() {
   221  		t.Fatal("not an extended private key")
   222  	}
   223  	if !isRange {
   224  		t.Fatal("not a range path")
   225  	}
   226  	parentPath, isRange, err := ParsePath(pathStr)
   227  	if err != nil {
   228  		t.Fatal(err)
   229  	}
   230  	if isRange {
   231  		t.Fatal("cleaned path was a range path")
   232  	}
   233  
   234  	// addrPath:    b940190e/84'/1'/0'/0/0   <-- child index at end
   235  	// parentPath:  b940190e/84'/1'/0'/0
   236  	if len(addrPath) != len(parentPath)+1 {
   237  		t.Fatal("paths do not agree")
   238  	}
   239  	for i := range parentPath {
   240  		if addrPath[i] != parentPath[i] {
   241  			t.Errorf("path depth %d incorrect, want %d, got %d", i, addrPath[i], parentPath[i])
   242  		}
   243  	}
   244  	childIdx := addrPath[len(addrPath)-1]
   245  
   246  	// fingerprint := hex.EncodeToString(keyFingerprint(xPriv)) // this is what we need to get for each entry in listdescriptors
   247  	if addrOriginFP != fingerprint {
   248  		t.Fatalf("wrong fingerprint")
   249  	}
   250  
   251  	// now that we have the corresponding master private key, derive the branch
   252  	// extended private key, then the child address key itself.
   253  
   254  	branch, err := DeepChild(xPriv, parentPath)
   255  	if err != nil {
   256  		t.Fatalf("DeepChild: %v", err)
   257  	}
   258  
   259  	child, _ := branch.Derive(childIdx)
   260  	pubkey := pubKeyBytes(child)
   261  	pkh := btcutil.Hash160(pubkey)
   262  	addrWPKH, err := btcutil.NewAddressWitnessPubKeyHash(pkh, chainParams)
   263  	if err != nil {
   264  		t.Fatalf("NewAddressWitnessPubKeyHash: %v", err)
   265  	}
   266  	if addrWPKH.String() != addr.String() {
   267  		t.Fatalf("wrong address %s", addrWPKH)
   268  	}
   269  
   270  	privkey, err := child.ECPrivKey()
   271  	if err != nil {
   272  		t.Fatalf("ECPrivKey: %v", err)
   273  	}
   274  	pubkey = privkey.PubKey().SerializeCompressed()
   275  	// check against "pubkey" field of getaddressinfo result
   276  	if !bytes.Equal(pubkey, addrPubKey) {
   277  		t.Fatal("wrong address pubkey")
   278  	}
   279  }
   280  
   281  func TestParsePath(t *testing.T) {
   282  	tests := []struct {
   283  		name        string
   284  		path        string
   285  		wantPath    []uint32
   286  		wantIsRange bool
   287  		wantErr     bool
   288  	}{
   289  		{
   290  			"empty",
   291  			"",
   292  			nil,
   293  			false,
   294  			false,
   295  		}, {
   296  			"unprefixed", // like path from ParseDescriptor or ParseKeyExtended
   297  			"84'/1'/0'/0/0",
   298  			[]uint32{2147483732, 2147483649, 2147483648, 0, 0},
   299  			false,
   300  			false,
   301  		}, {
   302  			"m", // like "hdkeypath" in getaddressinfo response
   303  			"m/84'/1h/0'/0/0",
   304  			[]uint32{2147483732, 2147483649, 2147483648, 0, 0},
   305  			false,
   306  			false,
   307  		}, {
   308  			"/",
   309  			"/84'/1'/0'/0h/0",
   310  			[]uint32{2147483732, 2147483649, 2147483648, 2147483648, 0},
   311  			false,
   312  			false,
   313  		}, {
   314  			"range",
   315  			"m/84'/1'/0'/0/0/*",
   316  			[]uint32{2147483732, 2147483649, 2147483648, 0, 0},
   317  			true,
   318  			false,
   319  		},
   320  	}
   321  	for _, tt := range tests {
   322  		t.Run(tt.name, func(t *testing.T) {
   323  			gotPath, gotIsRange, err := ParsePath(tt.path)
   324  			if (err != nil) != tt.wantErr {
   325  				t.Errorf("ParsePath() error = %v, wantErr %v", err, tt.wantErr)
   326  				return
   327  			}
   328  			if !reflect.DeepEqual(gotPath, tt.wantPath) {
   329  				t.Errorf("ParsePath() gotPath = %v, want %v", gotPath, tt.wantPath)
   330  			}
   331  			if gotIsRange != tt.wantIsRange {
   332  				t.Errorf("ParsePath() gotIsRange = %v, want %v", gotIsRange, tt.wantIsRange)
   333  			}
   334  		})
   335  	}
   336  }
   337  
   338  func TestParseKeyExtended(t *testing.T) {
   339  	tests := []struct {
   340  		name            string
   341  		keyStr          string
   342  		wantFingerprint string
   343  		wantPath        string
   344  		wantIsRange     bool
   345  		wantErr         bool
   346  	}{
   347  		{
   348  			"tprv with ranged path",
   349  			"tprv8ZgxMBicQKsPdAQ2QZeTReB2hH2aKXBWGqgnrW1aYbutbC7YfUtPPJm1Nppb6eXy5hnLRrRqwCctBecfZV8HLNsLeivVhKT1BYFBiRbhUES/84'/1'/0'/0/*",
   350  			"b940190e",
   351  			"84'/1'/0'/0",
   352  			true,
   353  			false,
   354  		}, {
   355  			"just tprv",
   356  			"tprv8ZgxMBicQKsPdAQ2QZeTReB2hH2aKXBWGqgnrW1aYbutbC7YfUtPPJm1Nppb6eXy5hnLRrRqwCctBecfZV8HLNsLeivVhKT1BYFBiRbhUES",
   357  			"b940190e",
   358  			"",
   359  			false,
   360  			false,
   361  		}, {
   362  			"full tpub",
   363  			"tpubDCoAK65iKNvE5x3wnCb87xRwD8wKDEKUyymu49KSgj9c5PG7DbfnYvwoPjgZaGhgTR4GfAQECPxrya46jeyiVn7jT1wuLDvb5CjJG6Q8FbT/0/*",
   364  			"af5360d5",
   365  			"0",
   366  			true,
   367  			false,
   368  		}, {
   369  			"hex pub",
   370  			"030003429cd5d23b1a229ec88dba6f2b69fb539fe26cd80229267aa0c992dc26b2",
   371  			"",
   372  			"",
   373  			false,
   374  			true,
   375  		}, {
   376  			"wif priv",
   377  			"cSqGdZNwiMcqJqC1NCwigLtQWmooMNnz5jMVuzuLd4pRiLP7CgFM", // alpha harness addr key
   378  			"",
   379  			"",
   380  			false,
   381  			true,
   382  		},
   383  	}
   384  	for _, tt := range tests {
   385  		t.Run(tt.name, func(t *testing.T) {
   386  			_, gotFingerprint, gotPath, gotIsRange, err := ParseKeyExtended(tt.keyStr)
   387  			if (err != nil) != tt.wantErr {
   388  				t.Errorf("ParseKeyExtended() error = %v, wantErr %v", err, tt.wantErr)
   389  				return
   390  			}
   391  			if tt.wantErr {
   392  				return
   393  			}
   394  			if gotFingerprint != tt.wantFingerprint {
   395  				t.Errorf("ParseKeyExtended() gotFingerprint = %v, want %v", gotFingerprint, tt.wantFingerprint)
   396  			}
   397  			if gotPath != tt.wantPath {
   398  				t.Errorf("ParseKeyExtended() gotPath = %v, want %v", gotPath, tt.wantPath)
   399  			}
   400  			if gotIsRange != tt.wantIsRange {
   401  				t.Errorf("ParseKeyExtended() gotIsRange = %v, want %v", gotIsRange, tt.wantIsRange)
   402  			}
   403  		})
   404  	}
   405  }
   406  
   407  func Test_checkDescriptorKey(t *testing.T) {
   408  	tests := []struct {
   409  		name string
   410  		key  string
   411  		want KeyFmt
   412  	}{
   413  		{
   414  			"hex pub (compressed)",
   415  			"030003429cd5d23b1a229ec88dba6f2b69fb539fe26cd80229267aa0c992dc26b2",
   416  			KeyHexPub,
   417  		}, {
   418  			"hex pub (uncompressed)",
   419  			"040003429cd5d23b1a229ec88dba6f2b69fb539fe26cd80229267aa0c992dc26b2cd3d19f0341e9064e21400bcde458ec96c38c25924413440c47cf5358443e871",
   420  			KeyHexPub,
   421  		}, {
   422  			"wif priv",
   423  			"cSqGdZNwiMcqJqC1NCwigLtQWmooMNnz5jMVuzuLd4pRiLP7CgFM",
   424  			KeyWIFPriv,
   425  		}, {
   426  			"extended with path",
   427  			"tpubDCoAK65iKNvE5x3wnCb87xRwD8wKDEKUyymu49KSgj9c5PG7DbfnYvwoPjgZaGhgTR4GfAQECPxrya46jeyiVn7jT1wuLDvb5CjJG6Q8FbT/0/*",
   428  			KeyExtended,
   429  		}, {
   430  			"extended no path",
   431  			"tpubDCoAK65iKNvE5x3wnCb87xRwD8wKDEKUyymu49KSgj9c5PG7DbfnYvwoPjgZaGhgTR4GfAQECPxrya46jeyiVn7jT1wuLDvb5CjJG6Q8FbT",
   432  			KeyExtended,
   433  		}, {
   434  			"unknown",
   435  			"asdfsadfsadf",
   436  			KeyUnknown,
   437  		},
   438  	}
   439  	for _, tt := range tests {
   440  		t.Run(tt.name, func(t *testing.T) {
   441  			if got := checkDescriptorKey(tt.key); got != tt.want {
   442  				t.Errorf("checkDescriptorKey() = %v, want %v", got, tt.want)
   443  			}
   444  		})
   445  	}
   446  }