def randFieldElement(): """ randFieldElement returns a random element of the field underlying the given curve using the procedure given in [NSA] A.2.1. """ b = ByteArray(generateSeed(curve.BitSize // 8 + 8)) n = curve.N - 1 k = b.int() k = k % n k = k + 1 return k
class ExtendedKey: """ ExtendedKey houses all the information needed to support a BIP0044 hierarchical deterministic extended key. """ def __init__( self, privVer, pubVer, key, pubKey, chainCode, parentFP, depth, childNum, isPrivate, ): """ Args: privVer (byte-like): Network version bytes for extended priv keys. pubVer (byte-like): Network version bytes for extended pub keys. key (byte-like): The key. pubKey (byte-like): Will be the same as `key` for public key. Will be generated from key if zero is provided. chainCode (byte-like): Chain code for key derivation. parentFP (ByteArray): parent key fingerprint. depth (int): Key depth. childNum (int): Child number. isPrivate (bool): Whether the key is a private or public key. """ if len(privVer) != 4 or len(pubVer) != 4: msg = "Network version bytes of incorrect lengths {} and {}" raise DecredError(msg.format(len(privVer), len(pubVer))) self.privVer = ByteArray(privVer) self.pubVer = ByteArray(pubVer) self.key = ByteArray(key) self.pubKey = ByteArray(pubKey) if self.pubKey.iszero(): if isPrivate: self.pubKey = Curve.publicKey( self.key.int()).serializeCompressed() else: self.pubKey = self.key self.chainCode = ByteArray(chainCode) self.parentFP = ByteArray(parentFP) self.depth = depth self.childNum = childNum self.isPrivate = isPrivate @staticmethod def new(seed): """ new creates a new crypto.ExtendedKey. Implementation based on dcrd hdkeychain newMaster. The ExtendedKey created does not have a network specified. The extended key returned from newMaster can be used to generate coin-type and account keys in accordance with BIP-0032 and BIP-0044. Args: seed (bytes-like): A random seed from which the extended key is made. Returns: crypto.ExtendedKey: A master hierarchical deterministic key. """ rando.checkSeedLength(len(seed)) # First take the HMAC-SHA512 of the master key and the seed data: # SHA512 hash is 64 bytes. lr = hmacDigest(MASTER_KEY, seed) # Split "I" into two 32-byte sequences Il and Ir where: # Il = master secret key # Ir = master chain code lrLen = int(len(lr) / 2) secretKey = lr[:lrLen] chainCode = lr[lrLen:] # Ensure the key is usable. secretInt = int.from_bytes(secretKey, byteorder="big") if secretInt > MAX_SECRET_INT or secretInt <= 0: raise KeyLengthError("generated key was outside acceptable range") parentFp = bytes.fromhex("00 00 00 00") return ExtendedKey( privVer=ByteArray([0, 0, 0, 0]), pubVer=ByteArray([0, 0, 0, 0]), key=secretKey, pubKey="", chainCode=chainCode, parentFP=parentFp, depth=0, childNum=0, isPrivate=True, ) def setNetwork(self, netParams): """ Sets the privVer and pubVer fields. This should be used when deriving the coin-type extended keys from the root wallet key. Args: netParams (module): The network parameters. """ self.privVer = netParams.HDPrivateKeyID self.pubVer = netParams.HDPublicKeyID def deriveCoinTypeKey(self, netParams): """ First two hardened child derivations in accordance with BIP0044. Args: netParams (module): The network parameters. Returns: ExtendedKey: The coin-type key. """ coinType = netParams.SLIP0044CoinType if coinType > MAX_COIN_TYPE: raise ParameterRangeError("coinType too high. %i > %i" % (coinType, MAX_COIN_TYPE)) purpose = self.child(44 + HARDENED_KEY_START) # Derive the purpose key as a child of the master node. coinKey = purpose.child(coinType + HARDENED_KEY_START) coinKey.privVer = netParams.HDPrivateKeyID coinKey.pubVer = netParams.HDPublicKeyID return coinKey def child(self, i): """ Child returns a derived child extended key at the given index. When this extended key is a private extended key (as determined by the IsPrivate function (TODO: implement IsPrivate)), a private extended key will be derived. Otherwise, the derived extended key will also be a public extended key. When the index is greater than or equal to the HardenedKeyStart constant, the derived extended key will be a hardened extended key. It is only possible to derive a hardended extended key from a private extended key. Consequently, this function will throw an exception if a hardened child extended key is requested from a public extended key. A hardened extended key is useful since, as previously mentioned, it requires a parent private extended key to derive. In other words, normal child extended public keys can be derived from a parent public extended key (no knowledge of the parent private key) whereas hardened extended keys may not be. NOTE: There is an extremely small chance (< 1 in 2^127) the specific child index does not derive to a usable child. An exception will happen if this should occur, and the caller is expected to ignore the invalid child and simply increment to the next index. There are four scenarios that could happen here: 1) Private extended key -> Hardened child private extended key 2) Private extended key -> Non-hardened child private extended key 3) Public extended key -> Non-hardened child public extended key 4) Public extended key -> Hardened child public extended key (INVALID!) Args: i (int): Child number. Returns: ExtendedKey: The child key. """ # Case #4 is invalid, so error out early. # A hardened child extended key may not be created from a public # extended key. isChildHardened = i >= HARDENED_KEY_START if not self.isPrivate and isChildHardened: raise ParameterRangeError( "cannot generate hardened child from public extended key") # The data used to derive the child key depends on whether or not the # child is hardened per [BIP32]. # # For hardened children: # 0x00 || ser256(parentKey) || ser32(i) # # For normal children: # serP(parentPubKey) || ser32(i) keyLen = 33 data = ByteArray(bytearray(keyLen + 4)) if isChildHardened: # Case #1. # When the child is a hardened child, the key is known to be a # private key due to the above early return. Pad it with a # leading zero as required by [BIP32] for deriving the child. data[1] = self.key else: # Case #2 or #3. # This is either a public or private extended key, but in # either case, the data which is used to derive the child key # starts with the secp256k1 compressed public key bytes. data[0] = ByteArray(self.pubKey) data[keyLen] = ByteArray(i, length=len(data) - keyLen) data |= i # ByteArray will handle the type conversion. # Take the HMAC-SHA512 of the current key's chain code and the derived # data: # I = HMAC-SHA512(Key = chainCode, Data = data) ilr = ByteArray(hmacDigest(self.chainCode.b, data.b, hashlib.sha512)) # Split "I" into two 32-byte sequences Il and Ir where: # Il = intermediate key used to derive the child # Ir = child chain code il = ilr[:len(ilr) // 2] childChainCode = ilr[len(ilr) // 2:] # See CrazyKeyError docs for an explanation of this condition. if il.int() >= Curve.N or il.iszero(): raise CrazyKeyError( "ExtendedKey.child: generated Il outside valid range") # The algorithm used to derive the child key depends on whether or not # a private or public child is being derived. # # For private children: # childKey = parse256(Il) + parentKey # # For public children: # childKey = serP(point(parse256(Il)) + parentKey) isPrivate = False if self.isPrivate: # Case #1 or #2. # Add the parent private key to the intermediate private key to # derive the final child key. # # childKey = parse256(Il) + parenKey childKey = ByteArray((self.key.int() + il.int()) % Curve.N) isPrivate = True else: # Case #3. # Calculate the corresponding intermediate public key for # intermediate private key. x, y = Curve.scalarBaseMult(il.int()) # Curve.G as ECPointJacobian if x == 0 or y == 0: raise ParameterRangeError( "ExtendedKey.child: generated pt outside valid range") # Convert the serialized compressed parent public key into X # and Y coordinates so it can be added to the intermediate # public key. pubKey = Curve.parsePubKey(self.key) # Add the intermediate public key to the parent public key to # derive the final child key. # # childKey = serP(point(parse256(Il)) + parentKey) # childX, childY := curve.Add(pt.x, pt.y, pubKey.X, pubKey.Y) childX, childY = Curve.add(x, y, pubKey.x, pubKey.y) # pk := secp256k1.PublicKey{Curve: secp256k1.S256(), X: childX, Y: childY} # childKey = pk.SerializeCompressed() childKey = PublicKey(Curve, childX, childY).serializeCompressed() # The fingerprint of the parent for the derived child is the first 4 # bytes of the RIPEMD160(BLAKE256(parentPubKey)). parentFP = hash160(self.pubKey.b)[:4] return ExtendedKey( privVer=self.privVer, pubVer=self.pubVer, key=childKey, pubKey="", chainCode=childChainCode, parentFP=parentFP, depth=self.depth + 1, childNum=i, isPrivate=isPrivate, ) def deriveAccountKey(self, acct): """ deriveAccountKey derives the extended key for an account according to the hierarchy described by BIP0044 given the master node. In particular this is the hierarchical deterministic extended key path: m/44'/<coin type>'/<account>' Args: account (int): Account number. Returns: ExtendedKey: Account key. """ # Enforce maximum account number. if acct > MAX_ACCOUNT_NUM: raise ParameterRangeError( "deriveAccountKey: account number greater than MAX_ACCOUNT_NUM" ) # Derive the account key as a child of the coin type key. return self.child(acct + HARDENED_KEY_START) def neuter(self): """ neuter returns a new extended public key from this extended private key. The same extended key will be returned unaltered if it is already an extended public key. As the name implies, an extended public key does not have access to the private key, so it is not capable of signing transactions or deriving child extended private keys. However, it is capable of deriving further child extended public keys. Returns: ExtendedKey: The public extended key. """ # Already an extended public key. if not self.isPrivate: return self # Convert it to an extended public key. The key for the new extended # key will simply be the pubkey of the current extended private key. # # This is the function N((k,c)) -> (K, c) from [BIP32]. return ExtendedKey( privVer=self.privVer, pubVer=self.pubVer, key=self.pubKey, pubKey=self.pubKey, chainCode=self.chainCode, parentFP=self.parentFP, depth=self.depth, childNum=self.childNum, isPrivate=False, ) def serialize(self): """ Return the extended key in serialized form. Returns: ByteArray: The serialized extended key. """ if self.key.iszero(): raise DecredError("unexpected zero key") childNumBytes = ByteArray(self.childNum, length=4) depthByte = ByteArray(self.depth % 256, length=1) # The serialized format is: # version (4) || depth (1) || parent fingerprint (4)) || # child num (4) || chain code (32) || key data (33) || checksum (4) serializedBytes = ByteArray( bytearray(0)) # length serializedKeyLen + 4 after appending if self.isPrivate: serializedBytes += self.privVer else: serializedBytes += self.pubVer serializedBytes += depthByte serializedBytes += self.parentFP serializedBytes += childNumBytes serializedBytes += self.chainCode if self.isPrivate: serializedBytes += bytearray(1) serializedBytes += self.key else: serializedBytes += self.pubKey checkSum = checksum(serializedBytes.b)[:4] serializedBytes += checkSum return serializedBytes def string(self): """ string returns the extended key as a base58-encoded string. See `decodeExtendedKey` for decoding. Returns: str: The encoded extended key. """ return b58encode(self.serialize().bytes()).decode() def privateKey(self): """ A PrivateKey structure that can be used for signatures. Returns: secp256k1.PrivateKey: The private key structure. """ return privKeyFromBytes(self.key) def publicKey(self): """ A PublicKey structure of the pubKey. Returns: secp256k1.PublicKey: The public key structure. """ return Curve.parsePubKey(self.pubKey)