decred.org/dcrdex@v1.0.3/dex/networks/btc/descriptors.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 "encoding/hex" 8 "errors" 9 "fmt" 10 "regexp" 11 "strconv" 12 "strings" 13 14 "github.com/btcsuite/btcd/btcec/v2" 15 "github.com/btcsuite/btcd/btcutil" 16 "github.com/btcsuite/btcd/btcutil/hdkeychain" 17 ) 18 19 var ( 20 // ErrMalformedDescriptor is returned when a provided descriptor string does 21 // not match the expected format for a descriptor. 22 ErrMalformedDescriptor = errors.New("malformed descriptor") 23 ) 24 25 var ( 26 hardened uint32 = hdkeychain.HardenedKeyStart 27 28 // {function}([{fingerprint}/{path}]{keyScriptOrNested})#{checksum} 29 descRE = regexp.MustCompile(`([[:alnum:]]+)` + `\((?:\[([[:xdigit:]]{8})/?([\d'h/]*)\])?(\S*)\)` + 30 `(?:#([[:alnum:]]{8}))?`) // https://regex101.com/r/75FEc4/1 31 32 // {xkey}{path}{range} e.g. {tpubDCD...}{/1'/2}{/*} 33 extKeyRE = regexp.MustCompile(`([[:alnum:]]+)((?:/\d+['h]?)*)(/\*['h]?)?`) // https://regex101.com/r/HLNzFF/1 34 ) 35 36 // KeyOrigin describes the optional part of a KEY expression that contains key 37 // origin information in side functions like pkh(KEY). For example, in the 38 // descriptor wpkh([b940190e/84'/1'/0'/0/0]0300034...) the key origin is 39 // [b940190e/84'/1'/0'/0/0], where the first part must be 8 hexadecimal 40 // characters for the fingerprint of the master key, and there are zero or more 41 // derivation steps to reach the key. See 42 // https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md#key-origin-identification 43 // Note for Fingerprint: "software must be willing to deal with collisions". 44 type KeyOrigin struct { 45 // Fingerprint is "Exactly 8 hex characters for the fingerprint of the key 46 // where the derivation starts". 47 Fingerprint string 48 // Path is "zero or more /NUM or /NUM' path elements to indicate unhardened 49 // or hardened derivation steps between the fingerprint and the key or 50 // xpub/xprv root that follows". ParseDescriptor will strip a leading "/". 51 Path string 52 // Steps is the parsed path. Hardened keys indexes are offset by 2^31. 53 Steps []uint32 54 } 55 56 // String creates the canonical string representation of the key origin, e.g. 57 // [d34db33f/44'/0'/0] for a Key origin with a Fingerprint of d34db33f and a 58 // Path of 44'/0'/0. 59 func (ko *KeyOrigin) String() string { 60 if ko.Path == "" { 61 return fmt.Sprintf("[%s]", ko.Fingerprint) 62 } 63 return fmt.Sprintf("[%s/%s]", ko.Fingerprint, strings.TrimPrefix(ko.Path, "/")) 64 } 65 66 // KeyFmt specifies the encoding of the key within a KEY expression. 67 type KeyFmt byte 68 69 // Valid KeyFmt values are one of: 70 // 1. KeyHexPub: hex-encoded public key, compressed or uncompressed 71 // 2. KeyWIFPriv: WIF-encoded private key 72 // 3. KeyExtended public or private extended (BIP32) public key, PLUS zero or 73 // more /NUM or /NUM' steps, optionally terminated with a /* or /' to 74 // indicate all direct children (a range). 75 const ( 76 KeyUnknown KeyFmt = iota 77 KeyWIFPriv 78 KeyHexPub 79 KeyExtended 80 ) 81 82 // pubKeyBytes is a roundabout helper since ExtendedKey.pubKeyBytes() is not 83 // exported. The extended key should be created with an hdkeychain constructor 84 // so the pubkey is always valid. 85 func pubKeyBytes(key *hdkeychain.ExtendedKey) []byte { 86 pk, _ := key.ECPubKey() 87 return pk.SerializeCompressed() 88 } 89 90 func keyFingerprint(key *hdkeychain.ExtendedKey) []byte { 91 return btcutil.Hash160(pubKeyBytes(key))[:4] 92 } 93 94 // ParseKeyExtended is used to decode a Descriptor.Key when KeyFmt is 95 // KeyExtended, which is the standard BIP32 extended key encoding with optional 96 // derivation steps and a range indicator appended. The fingerprint of the 97 // extended key is returned as a convenience. Use ParsePath to get the 98 // derivation steps. 99 // e.g. "tpubDCo.../0/*" is an extended public key with a ranged path. 100 func ParseKeyExtended(keyPart string) (key *hdkeychain.ExtendedKey, fingerprint, path string, isRange bool, err error) { 101 xkeyParts := extKeyRE.FindStringSubmatch(keyPart) 102 if len(xkeyParts) < 4 { // extKeyRE has 3 capture groups + the match itself 103 return nil, "", "", false, ErrMalformedDescriptor 104 } 105 106 key, err = hdkeychain.NewKeyFromString(xkeyParts[1]) 107 if err != nil { 108 return nil, "", "", false, err 109 } 110 fingerprint = hex.EncodeToString(keyFingerprint(key)) 111 path = strings.TrimPrefix(xkeyParts[2], "/") // extKeyRE captures the leading "/" 112 isRange = len(xkeyParts[3]) > 0 113 return 114 } 115 116 func checkDescriptorKey(key string) KeyFmt { 117 // KeyWIFPriv 118 _, err := btcutil.DecodeWIF(key) 119 if err == nil { 120 return KeyWIFPriv 121 } 122 123 // KeyHexPub 124 if pkBytes, err := hex.DecodeString(key); err == nil { 125 if _, err = btcec.ParsePubKey(pkBytes); err == nil { 126 return KeyHexPub 127 } 128 } 129 130 // KeyExtended 131 if _, _, _, _, err = ParseKeyExtended(key); err == nil { 132 return KeyExtended 133 } 134 135 return KeyUnknown 136 } 137 138 // Descriptor models the output description language used by Bitcoin Core 139 // descriptor wallets. Descriptors are a textual representation of an output or 140 // address that begins with a "function", which may take as an argument a KEY, 141 // SCRIPT, or other data specific to the function. This Descriptor type is 142 // provided to decode and represent the most common KEY descriptor types and 143 // SCRIPT types that commonly wrap other KEY types. 144 type Descriptor struct { 145 // Function is the name of the top level function that begins the 146 // descriptor. For example, "pk", "pkh", "wpkh", "sh", "wsh", etc. 147 Function string 148 // Key is set for the KEY type functions and certain SCRIPT functions with 149 // nested KEY functions. May include a suffixed derivation path. 150 Key string 151 // KeyFmt is the type of key encoding. 152 KeyFmt KeyFmt 153 // KeyOrigin is an optional part of a KEY descriptor that describes the 154 // derivation of the key. 155 KeyOrigin *KeyOrigin 156 // Nested will only be set for descriptors with SCRIPT expressions. 157 Nested *Descriptor 158 // Expression is the entirety of the arguments to Function. This may be a 159 // KEY, SCRIPT, TREE, or combination of arguments depending on the function. 160 Expression string 161 // Checksum is an optional 8-character alphanumeric checksum of the 162 // descriptor. It is not validated. 163 Checksum string 164 } 165 166 func parseDescriptor(desc string, parentFn string) (*Descriptor, error) { 167 parts := descRE.FindStringSubmatch(desc) 168 if len(parts) < 6 { // descRE has 5 capture groups + the match itself 169 return nil, ErrMalformedDescriptor 170 } 171 parts = parts[1:] // pop off the match itself, just check capture groups 172 173 // function([fingerprint/path]arg)#checksum 174 function, fingerprint, path := parts[0], parts[1], parts[2] 175 arg, checksum := parts[3], parts[4] 176 177 d := &Descriptor{ 178 Function: function, 179 Expression: arg, 180 Checksum: checksum, 181 } 182 183 switch function { 184 case "pk", "pkh", "wpkh", "combo": // KEY functions 185 d.Key = arg 186 // Be forward compatible: key format may be KeyUnknown, but the 187 // descriptor format is otherwise valid. 188 d.KeyFmt = checkDescriptorKey(arg) 189 190 if fingerprint != "" { 191 steps, isRange, err := parsePath(path) // descRE discards the leading "/" 192 if err != nil { 193 return nil, err 194 } 195 if isRange { 196 return nil, errors.New("range in key origin") 197 } 198 199 d.KeyOrigin = &KeyOrigin{ 200 Fingerprint: fingerprint, 201 Path: path, 202 Steps: steps, 203 } 204 205 // Rebuild the full argument to the key function. 206 // e.g. "[b940190e/84'/1'/0'/0/0]" + "030003..."" 207 d.Expression = d.KeyOrigin.String() + d.Key 208 } 209 210 // SCRIPT functions that may have nested KEY function 211 case "sh": 212 // sh is "top level only" 213 if parentFn != "" { 214 return nil, errors.New("invalid nested scripthash") 215 } 216 217 fallthrough 218 case "wsh": 219 // wsh is "top level or inside sh only" 220 switch parentFn { 221 case "", "sh": 222 default: 223 return nil, errors.New("invalid nested witness scripthash") 224 } 225 226 if fingerprint != "" { 227 return nil, errors.New("key origin found in SCRIPT function") 228 } 229 230 var err error 231 d.Nested, err = parseDescriptor(arg, function) 232 if err != nil { 233 return nil, err 234 } 235 // If child SCRIPT was a KEY function, pull it up. 236 if d.Nested.KeyFmt != KeyUnknown { 237 d.KeyOrigin = d.Nested.KeyOrigin 238 d.Key = d.Nested.Key 239 d.KeyFmt = d.Nested.KeyFmt 240 } 241 242 default: // not KEY, like multi, tr, etc. 243 } 244 245 return d, nil 246 } 247 248 // ParseDescriptor parses a descriptor string. See 249 // https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md 250 // If the descriptor string does not match the expected pattern, an 251 // ErrMalformedDescriptor error is returned. See the Descriptor type 252 // documentation for information. 253 func ParseDescriptor(desc string) (*Descriptor, error) { 254 return parseDescriptor(desc, "") 255 } 256 257 func parsePathPieces(pieces []string) (path []uint32, isRange bool, err error) { 258 for i, p := range pieces { 259 // In descriptor paths, "Anywhere a ' suffix is permitted to denote 260 // hardened derivation, the suffix h can be used instead." 261 if p == "*" || p == "*'" || p == "*h" { 262 if i != len(pieces)-1 { 263 return nil, false, errors.New("range indicator before end of path") 264 } 265 isRange = true 266 break // end of path, this is all the addresses 267 } 268 pN := strings.TrimRight(p, "'h") // strip any hardened character 269 pi, err := strconv.ParseUint(pN, 10, 32) 270 if err != nil { 271 return nil, false, err 272 } 273 if pN != p { // we stripped a ' or h => hardened 274 pi += uint64(hardened) 275 } 276 path = append(path, uint32(pi)) 277 } 278 return 279 } 280 281 func parsePath(p string) (path []uint32, isRange bool, err error) { 282 if len(p) == 0 { 283 return 284 } 285 pp := strings.Split(p, "/") 286 return parsePathPieces(pp) 287 } 288 289 // ParsePath splits a common path string such as 84'/0'/0'/0 into it's 290 // components, returning the steps in the derivation as integers. Hardened key 291 // derivation steps, which are indicated by a ' or h, are offset by 2^31. 292 // Certain paths in descriptors may end with /* (or /*' or /*h) to indicate all 293 // child keys (or hardened child keys), in which case isRange will be true. As a 294 // special case, a prefix of "m/" or just "/" is allowed, but a master key 295 // fingerprint is not permitted and must be stripped first. 296 func ParsePath(p string) (path []uint32, isRange bool, err error) { 297 p = strings.TrimPrefix(strings.TrimPrefix(p, "m"), "/") 298 return parsePath(p) 299 } 300 301 // DeepChild derives a new extended key from the provided root extended key and 302 // derivation path. This is useful given a Descriptor.Key of type KeyExtended 303 // that may be decoded into and extended key and path with ParseKeyExtended. 304 // Given an address referencing the extended key via its fingerprint (also 305 // returned by ParseKeyExtended) and derivation path, the private key for that 306 // address may be derived given the child index of the address. 307 func DeepChild(root *hdkeychain.ExtendedKey, path []uint32) (*hdkeychain.ExtendedKey, error) { 308 genChild := func(parent *hdkeychain.ExtendedKey, childIdx uint32) (*hdkeychain.ExtendedKey, error) { 309 err := hdkeychain.ErrInvalidChild 310 for err == hdkeychain.ErrInvalidChild { 311 var kid *hdkeychain.ExtendedKey 312 kid, err = parent.Derive(childIdx) 313 if err == nil { 314 return kid, nil 315 } 316 fmt.Printf("Child derive skipped a key index %d -> %d", childIdx, childIdx+1) // < 1 in 2^127 chance 317 childIdx++ 318 } 319 return nil, err 320 } 321 322 extKey := root 323 for i, childIdx := range path { 324 childExtKey, err := genChild(extKey, childIdx) 325 if i > 0 { // don't zero the input key, root 326 extKey.Zero() 327 } 328 extKey = childExtKey 329 if err != nil { 330 if i > 0 { 331 extKey.Zero() 332 } 333 return nil, err 334 } 335 } 336 337 return extKey, nil 338 }