package hd import ( "bytes" "encoding/hex" "testing" ) // seed is the BIP32 test vector 1 seed. var seed = mustHex("000102030405060708090a0b0c0d0e0f") // mustHex decodes a hex string or fails the test. func mustHex(text string) []byte { out, err := hex.DecodeString(text) if err != nil { panic(err) } return out } // TestMasterIsDeterministic: the same seed is the same tree, which is the whole promise of // a backup. func TestMasterIsDeterministic(t *testing.T) { first, err := NewMaster(seed) if err != nil { t.Fatal(err) } second, err := NewMaster(seed) if err != nil { t.Fatal(err) } if !bytes.Equal(first.Private[:], second.Private[:]) || !bytes.Equal(first.ChainCode[:], second.ChainCode[:]) { t.Fatal("the same seed gave two master keys") } if first.Depth != 0 || len(first.Path) != 0 { t.Fatalf("the master key is at depth %d, path %v", first.Depth, first.Path) } // A different seed is a different tree. other, err := NewMaster(mustHex("0f0e0d0c0b0a09080706050403020100")) if err != nil { t.Fatal(err) } if bytes.Equal(first.Private[:], other.Private[:]) { t.Fatal("two different seeds gave the same master key") } } // TestHardenedAndNormalDiffer: the same index on the two branches is two keys, which is // what a hardened level buys. func TestHardenedAndNormalDiffer(t *testing.T) { master, err := NewMaster(seed) if err != nil { t.Fatal(err) } normal, err := master.ChildKey(0) if err != nil { t.Fatal(err) } hardened, err := master.ChildKey(Hardened) if err != nil { t.Fatal(err) } if bytes.Equal(normal.Private[:], hardened.Private[:]) { t.Fatal("the normal and hardened children of one index are the same key") } if normal.Depth != 1 || hardened.Depth != 1 { t.Fatalf("depths are %d and %d", normal.Depth, hardened.Depth) } } // TestPathIsCarried: a key says where it came from, because a wallet's address is recorded // as a path. func TestPathIsCarried(t *testing.T) { master, err := NewMaster(seed) if err != nil { t.Fatal(err) } path, err := ParsePath("m/84'/0'/0'/1/7") if err != nil { t.Fatal(err) } key, err := master.Derive(path) if err != nil { t.Fatal(err) } if got := key.ChildPath(); got != "m/84'/0'/0'/1/7" { t.Fatalf("the key's path is %q", got) } if key.Depth != 5 { t.Fatalf("the depth is %d", key.Depth) } // The same path twice is the same key: derivation is a function, not a walk. again, err := master.Derive(path) if err != nil { t.Fatal(err) } if !bytes.Equal(key.Private[:], again.Private[:]) { t.Fatal("the same path gave two keys") } } // TestParsePath: the notation a person copies, and what it refuses. func TestParsePath(t *testing.T) { good := map[string]string{ "m/84'/0'/0'/0/0": "m/84'/0'/0'/0/0", "m/84h/0h/0h/0/0": "m/84'/0'/0'/0/0", "m": "m", " m/0 ": "m/0", } for input, want := range good { path, err := ParsePath(input) if err != nil { t.Fatalf("%q: %v", input, err) } if got := path.String(); got != want { t.Fatalf("%q parsed to %q, want %q", input, got, want) } } for _, bad := range []string{ "", "84'/0'", "m//0", "m/x", "m/84''", "m/2147483648", "/0", "m/-1", } { if _, err := ParsePath(bad); err == nil { t.Errorf("%q was accepted as a path", bad) } } } // TestPublicIsStable: the same private key is the same public key, and a child's is not // its parent's. func TestPublicIsStable(t *testing.T) { master, err := NewMaster(seed) if err != nil { t.Fatal(err) } first := master.Public() second := master.Public() if !bytes.Equal(first, second) || len(first) != 33 { t.Fatalf("the public key is %d bytes and %v", len(first), bytes.Equal(first, second)) } child, err := master.ChildKey(Hardened) if err != nil { t.Fatal(err) } if bytes.Equal(child.Public(), first) { t.Fatal("a child has its parent's public key") } // A public key is a compressed point, so its first byte is a parity marker. if first[0] != 0x02 && first[0] != 0x03 { t.Fatalf("the public key starts with %#x", first[0]) } } // TestSeedLength: BIP32 defines 16 to 64 bytes. func TestSeedLength(t *testing.T) { for _, size := range []int{16, 32, 64} { if _, err := NewMaster(make([]byte, size)); err != nil { t.Errorf("%d bytes: %v", size, err) } } for _, size := range []int{0, 15, 65} { if _, err := NewMaster(make([]byte, size)); err == nil { t.Errorf("%d bytes was accepted", size) } } // An all-zero seed is still a seed: it is a bad one for a person to pick, but it is // representable, and refusing it would refuse a valid derivation. if _, err := NewMaster(make([]byte, 32)); err != nil { t.Fatalf("the zero seed was refused: %v", err) } }