github.com/Finschia/finschia-sdk@v0.48.1/crypto/hd/hdpath_test.go (about) 1 package hd_test 2 3 import ( 4 "encoding/hex" 5 "fmt" 6 "testing" 7 8 "github.com/Finschia/finschia-sdk/crypto/hd" 9 "github.com/Finschia/finschia-sdk/types" 10 11 bip39 "github.com/cosmos/go-bip39" 12 "github.com/stretchr/testify/require" 13 ) 14 15 var defaultBIP39Passphrase = "" 16 17 // return bip39 seed with empty passphrase 18 func mnemonicToSeed(mnemonic string) []byte { 19 return bip39.NewSeed(mnemonic, defaultBIP39Passphrase) 20 } 21 22 func TestStringifyFundraiserPathParams(t *testing.T) { 23 path := hd.NewFundraiserParams(4, types.CoinType, 22) 24 require.Equal(t, "m/44'/438'/4'/0/22", path.String()) 25 26 path = hd.NewFundraiserParams(4, types.CoinType, 57) 27 require.Equal(t, "m/44'/438'/4'/0/57", path.String()) 28 29 path = hd.NewFundraiserParams(4, 12345, 57) 30 require.Equal(t, "m/44'/12345'/4'/0/57", path.String()) 31 } 32 33 func TestPathToArray(t *testing.T) { 34 path := hd.NewParams(44, 118, 1, false, 4) 35 require.Equal(t, "[44 118 1 0 4]", fmt.Sprintf("%v", path.DerivationPath())) 36 37 path = hd.NewParams(44, 118, 2, true, 15) 38 require.Equal(t, "[44 118 2 1 15]", fmt.Sprintf("%v", path.DerivationPath())) 39 } 40 41 func TestParamsFromPath(t *testing.T) { 42 goodCases := []struct { 43 params *hd.BIP44Params 44 path string 45 }{ 46 {&hd.BIP44Params{44, 0, 0, false, 0}, "m/44'/0'/0'/0/0"}, 47 {&hd.BIP44Params{44, 1, 0, false, 0}, "m/44'/1'/0'/0/0"}, 48 {&hd.BIP44Params{44, 0, 1, false, 0}, "m/44'/0'/1'/0/0"}, 49 {&hd.BIP44Params{44, 0, 0, true, 0}, "m/44'/0'/0'/1/0"}, 50 {&hd.BIP44Params{44, 0, 0, false, 1}, "m/44'/0'/0'/0/1"}, 51 {&hd.BIP44Params{44, 1, 1, true, 1}, "m/44'/1'/1'/1/1"}, 52 {&hd.BIP44Params{44, 118, 52, true, 41}, "m/44'/118'/52'/1/41"}, 53 } 54 55 for i, c := range goodCases { 56 params, err := hd.NewParamsFromPath(c.path) 57 errStr := fmt.Sprintf("%d %v", i, c) 58 require.NoError(t, err, errStr) 59 require.EqualValues(t, c.params, params, errStr) 60 require.Equal(t, c.path, c.params.String()) 61 } 62 63 badCases := []struct { 64 path string 65 }{ 66 {"m/43'/0'/0'/0/0"}, // doesn't start with 44 67 {"m/44'/1'/0'/0/0/5"}, // too many fields 68 {"m/44'/0'/1'/0"}, // too few fields 69 {"m/44'/0'/0'/2/0"}, // change field can only be 0/1 70 {"m/44/0'/0'/0/0"}, // first field needs ' 71 {"m/44'/0/0'/0/0"}, // second field needs ' 72 {"m/44'/0'/0/0/0"}, // third field needs ' 73 {"m/44'/0'/0'/0'/0"}, // fourth field must not have ' 74 {"m/44'/0'/0'/0/0'"}, // fifth field must not have ' 75 {"m/44'/-1'/0'/0/0"}, // no negatives 76 {"m/44'/0'/0'/-1/0"}, // no negatives 77 {"m/a'/0'/0'/-1/0"}, // invalid values 78 {"m/0/X/0'/-1/0"}, // invalid values 79 {"m/44'/0'/X/-1/0"}, // invalid values 80 {"m/44'/0'/0'/%/0"}, // invalid values 81 {"m/44'/0'/0'/0/%"}, // invalid values 82 {"m44'0'0'00"}, // no separators 83 {" /44'/0'/0'/0/0"}, // blank first component 84 } 85 86 for i, c := range badCases { 87 params, err := hd.NewParamsFromPath(c.path) 88 errStr := fmt.Sprintf("%d %v", i, c) 89 require.Nil(t, params, errStr) 90 require.Error(t, err, errStr) 91 } 92 } 93 94 func TestCreateHDPath(t *testing.T) { 95 type args struct { 96 coinType uint32 97 account uint32 98 index uint32 99 } 100 tests := []struct { 101 name string 102 args args 103 want hd.BIP44Params 104 }{ 105 {"m/44'/0'/0'/0/0", args{0, 0, 0}, hd.BIP44Params{Purpose: 44}}, 106 {"m/44'/114'/0'/0/0", args{114, 0, 0}, hd.BIP44Params{Purpose: 44, CoinType: 114, Account: 0, AddressIndex: 0}}, 107 {"m/44'/114'/1'/1/0", args{114, 1, 1}, hd.BIP44Params{Purpose: 44, CoinType: 114, Account: 1, AddressIndex: 1}}, 108 } 109 for _, tt := range tests { 110 tt := tt 111 t.Run(tt.name, func(t *testing.T) { 112 tt := tt 113 require.Equal(t, tt.want, *hd.CreateHDPath(tt.args.coinType, tt.args.account, tt.args.index)) 114 }) 115 } 116 } 117 118 // Tests to ensure that any index value is in the range [0, max(int32)] as per 119 // the extended keys specification. If the index belongs to that of a hardened key, 120 // its 0x80000000 bit will be set, so we can still accept values in [0, max(int32)] and then 121 // increase its value as deriveKeyPath already augments. 122 // See issue https://github.com/cosmos/cosmos-sdk/issues/7627. 123 func TestDeriveHDPathRange(t *testing.T) { 124 seed := mnemonicToSeed("I am become Death, the destroyer of worlds!") 125 126 tests := []struct { 127 path string 128 wantErr string 129 }{ 130 { 131 path: "m/1'/2147483648/0'/0/0", 132 wantErr: "out of range", 133 }, 134 { 135 path: "m/2147483648'/1/0/0", 136 wantErr: "out of range", 137 }, 138 { 139 path: "m/2147483648'/2147483648/0'/0/0", 140 wantErr: "out of range", 141 }, 142 { 143 path: "m/1'/-5/0'/0/0", 144 wantErr: "invalid syntax", 145 }, 146 { 147 path: "m/-2147483646'/1/0/0", 148 wantErr: "invalid syntax", 149 }, 150 { 151 path: "m/-2147483648'/-2147483648/0'/0/0", 152 wantErr: "invalid syntax", 153 }, 154 { 155 path: "m44'118'0'00", 156 wantErr: "path 'm44'118'0'00' doesn't contain '/' separators", 157 }, 158 { 159 path: "", 160 wantErr: "path '' doesn't contain '/' separators", 161 }, 162 { 163 // Should pass. 164 path: "m/1'/2147483647'/1/0'/0/0", 165 }, 166 { 167 // Should pass. 168 path: "1'/2147483647'/1/0'/0/0", 169 }, 170 } 171 172 for _, tt := range tests { 173 tt := tt 174 t.Run(tt.path, func(t *testing.T) { 175 master, ch := hd.ComputeMastersFromSeed(seed) 176 _, err := hd.DerivePrivateKeyForPath(master, ch, tt.path) 177 178 if tt.wantErr == "" { 179 require.NoError(t, err, "unexpected error") 180 } else { 181 require.Error(t, err, "expected a report of an int overflow") 182 require.Contains(t, err.Error(), tt.wantErr) 183 } 184 }) 185 } 186 } 187 188 func ExampleStringifyPathParams() { 189 path := hd.NewParams(44, 0, 0, false, 0) 190 fmt.Println(path.String()) 191 path = hd.NewParams(44, 33, 7, true, 9) 192 fmt.Println(path.String()) 193 // Output: 194 // m/44'/0'/0'/0/0 195 // m/44'/33'/7'/1/9 196 } 197 198 func ExampleSomeBIP32TestVecs() { 199 seed := mnemonicToSeed("barrel original fuel morning among eternal " + 200 "filter ball stove pluck matrix mechanic") 201 master, ch := hd.ComputeMastersFromSeed(seed) 202 fmt.Println("keys from fundraiser test-vector (link, bitcoin, ether)") 203 fmt.Println() 204 // cosmos 205 priv, err := hd.DerivePrivateKeyForPath(master, ch, types.FullFundraiserPath) 206 if err != nil { 207 fmt.Println("INVALID") 208 } else { 209 fmt.Println(hex.EncodeToString(priv[:])) 210 } 211 // bitcoin 212 priv, err = hd.DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0") 213 if err != nil { 214 fmt.Println("INVALID") 215 } else { 216 fmt.Println(hex.EncodeToString(priv[:])) 217 } 218 // ether 219 priv, err = hd.DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0") 220 if err != nil { 221 fmt.Println("INVALID") 222 } else { 223 fmt.Println(hex.EncodeToString(priv[:])) 224 } 225 // INVALID 226 priv, err = hd.DerivePrivateKeyForPath(master, ch, "X/0'/0'/0/0") 227 if err != nil { 228 fmt.Println("INVALID") 229 } else { 230 fmt.Println(hex.EncodeToString(priv[:])) 231 } 232 priv, err = hd.DerivePrivateKeyForPath(master, ch, "-44/0'/0'/0/0") 233 if err != nil { 234 fmt.Println("INVALID") 235 } else { 236 fmt.Println(hex.EncodeToString(priv[:])) 237 } 238 239 fmt.Println() 240 fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html") 241 fmt.Println() 242 243 seed = mnemonicToSeed( 244 "advice process birth april short trust crater change bacon monkey medal garment " + 245 "gorilla ranch hour rival razor call lunar mention taste vacant woman sister") 246 master, ch = hd.ComputeMastersFromSeed(seed) 247 priv, _ = hd.DerivePrivateKeyForPath(master, ch, "44'/1'/1'/0/4") 248 fmt.Println(hex.EncodeToString(priv[:])) 249 250 seed = mnemonicToSeed("idea naive region square margin day captain habit " + 251 "gun second farm pact pulse someone armed") 252 master, ch = hd.ComputeMastersFromSeed(seed) 253 priv, _ = hd.DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420") 254 fmt.Println(hex.EncodeToString(priv[:])) 255 256 fmt.Println() 257 fmt.Println("BIP 32 example") 258 fmt.Println() 259 260 // bip32 path: m/0/7 261 seed = mnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history") 262 master, ch = hd.ComputeMastersFromSeed(seed) 263 priv, _ = hd.DerivePrivateKeyForPath(master, ch, "0/7") 264 fmt.Println(hex.EncodeToString(priv[:])) 265 266 // Output: keys from fundraiser test-vector (link, bitcoin, ether) 267 // 268 // ae98c89aca32cb824f5b7ff94d7bf203e1f26d81787467d45422e4c7497342c9 269 // e77c3de76965ad89997451de97b95bb65ede23a6bf185a55d80363d92ee37c3d 270 // 7fc4d8a8146dea344ba04c593517d3f377fa6cded36cd55aee0a0bb968e651bc 271 // INVALID 272 // INVALID 273 // 274 // keys generated via https://coinomi.com/recovery-phrase-tool.html 275 // 276 // a61f10c5fecf40c084c94fa54273b6f5d7989386be4a37669e6d6f7b0169c163 277 // 32c4599843de3ef161a629a461d12c60b009b676c35050be5f7ded3a3b23501f 278 // 279 // BIP 32 example 280 // 281 // c4c11d8c03625515905d7e89d25dfc66126fbc629ecca6db489a1a72fc4bda78 282 } 283 284 // Ensuring that we don't crash if values have trailing slashes 285 // See issue https://github.com/cosmos/cosmos-sdk/issues/8557. 286 func TestDerivePrivateKeyForPathDoNotCrash(t *testing.T) { 287 paths := []string{ 288 "m/5/", 289 "m/5", 290 "/44", 291 "m//5", 292 "m/0/7", 293 "/", 294 " m  /0/7", // Test case from fuzzer 295 "  /  ", // Test case from fuzzer 296 "m///7//////", 297 } 298 299 for _, path := range paths { 300 path := path 301 t.Run(path, func(t *testing.T) { 302 hd.DerivePrivateKeyForPath([32]byte{}, [32]byte{}, path) 303 }) 304 } 305 }