def test_all(self): tests = [ ( "topmost Istanbul Pluto vagabond treadmill Pacific brackish dictator goldfish Medusa afflict bravado chatter revolver Dupont midsummer stopwatch whimsical cowbell bottomless", ByteArray([ 0xE5, 0x82, 0x94, 0xF2, 0xE9, 0xA2, 0x27, 0x48, 0x6E, 0x8B, 0x06, 0x1B, 0x31, 0xCC, 0x52, 0x8F, 0xD7, 0xFA, 0x3F, 0x19 ]), ), ( "stairway souvenir flytrap recipe adrift upcoming artist positive spearhead Pandora spaniel stupendous tonic concurrent transit Wichita lockup visitor flagpole escapade", ByteArray([ 0xD1, 0xD4, 0x64, 0xC0, 0x04, 0xF0, 0x0F, 0xB5, 0xC9, 0xA4, 0xC8, 0xD8, 0xE4, 0x33, 0xE7, 0xFB, 0x7F, 0xF5, 0x62, 0x56 ]), ), ] listToLower = lambda l: [x.lower() for x in l] for i, (words, seed) in enumerate(tests): unWords = encode(seed) self.assertListEqual(listToLower(unWords[:len(unWords)-1]), listToLower(words.split())) unSeed = decode(words.split()) self.assertEqual(seed, unSeed)
def tx(self, txid): """ Get the MsgTx. Retreive it from the blockchain if necessary. Args: txid (str): A hex encoded transaction ID to fetch. Returns: MsgTx: The transaction. """ hashKey = hashFromHex(txid).bytes() with self.txDB as txDB: try: encoded = ByteArray(txDB[hashKey]) return msgtx.MsgTx.deserialize(encoded) except database.NoValue: try: # Grab the hex encoded transaction txHex = self.dcrdata.tx.hex(txid) if not txHex: raise Exception( "failed to retrieve tx hex from dcrdata") encoded = ByteArray(txHex) txDB[hashKey] = encoded.bytes() return msgtx.MsgTx.deserialize(encoded) except: log.warning( "unable to retrieve tx data from dcrdata at %s" % self.dcrdata.baseUri) raise Exception("failed to reteive transaction")
def rekey(password, kp): """ Regenerate a key using its origin key parameters, as returned by `params`. Args: kp (KDFParams): The key parameters from the original generation of the key being regenerated. Returns: SecretKey: The regenerated key. """ sk = SecretKey(b"") sk.keyParams = kp b = lambda v: ByteArray(v).bytes() func = kp.kdfFunc if func == "pbkdf2_hmac": sk.key = ByteArray( hashlib.pbkdf2_hmac(kp.hashName, b(password), b(kp.salt), kp.iterations)) else: raise Exception("unkown key derivation function") checkDigest = ByteArray(hashlib.sha256(sk.key.b).digest()) if checkDigest != kp.digest: raise PasswordError("rekey digest check failed") return sk
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. """ assert len(privVer) == 4 and len( pubVer) == 4, "Network version bytes of incorrect length" 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
def blockHeaderByHeight(self, height): """ Get the block header by height. The blcck header is retreived from the blockchain if necessary, in which case it is stored. Args: height int: The block height Returns: BlockHeader: The block header. """ with self.heightMap as heightMap, self.headerDB as headers: try: hashKey = heightMap[height] serialized = headers[hashKey] return msgblock.BlockHeader.deserialize(serialized) except database.NoValue: try: hexBlock = self.blockchain.block.header.raw(idx=height) blockHeader = msgblock.BlockHeader.deserialize( ByteArray(hexBlock)) self.saveBlockHeader(blockHeader) return blockHeader except: log.warning("unable to retrieve block header") raise Exception("failed to get block header at height %i" % height)
def blockHeader(self, hexHash): """ blockHeader will produce a blockHeader implements the BlockHeader API. Args: bHash (str): The block hash of the block header. Returns: BlockHeader: An object which implements the BlockHeader API. """ with self.headerDB as headers: try: serialized = headers[hashFromHex(hexHash).bytes()] return msgblock.BlockHeader.deserialize(serialized) except database.NoValue: try: block = self.dcrdata.block.hash.header.raw(hexHash) blockHeader = msgblock.BlockHeader.deserialize( ByteArray(block["hex"])) self.saveBlockHeader(blockHeader) return blockHeader except Exception as e: log.warning("unable to retrieve block header: %s" % formatTraceback(e)) raise Exception("failed to get block header for block %s" % hexHash)
def openAccount(self, acct, pw): """ Open an account. Args: acct (int): The acccount index, which is its position in the accounts list. net (obj): Network parameters. pw (byte-like): An UTF-8-encoded user-supplied password for the account. Returns: Account: The open account. """ # A string at this point is considered to be ascii, not hex. if isinstance(pw, str): pw = pw.encode() # Generate the master key, which is used to decrypt the crypto keys. userSecret = crypto.SecretKey.rekey(pw, self.privParams) # Decrypt the crypto keys. cryptKeyPriv = ByteArray( userSecret.decrypt(self.cryptoKeyPrivEnc.bytes())) # Retreive and open the account. account = self.accounts[acct] account.open(cryptKeyPriv) return account
def test_decode(self): encoded = "060000000bd25508e99bf6f8399efce65762b55873d69dd05a7871631ac8fa7a36f1d05c977ea75040b905415cbc8f7dd519831a031ef5cd9c6a187a9eab8136c8b44fda000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000ffff7f20204e0000000000001b00000066010000aadd025d00000000255221163779dfe800000000000000000000000000000000000000000000000000000000" header = BlockHeader.deserialize(ByteArray(encoded)) print("version: %s" % repr(header.version)) print("prevBlock: %s" % repr(reversed(header.prevBlock).hex())) print("merkleRoot: %s" % repr(reversed(header.merkleRoot).hex())) print("stakeRoot: %s" % repr(reversed(header.stakeRoot).hex())) print("voteBits: %s" % repr(header.voteBits)) print("finalState: %s" % repr(reversed(header.finalState).hex())) print("voters: %s" % repr(header.voters)) print("freshStake: %s" % repr(header.freshStake)) print("revocations: %s" % repr(header.revocations)) print("poolSize: %s" % repr(header.poolSize)) print("bits: %s" % repr(header.bits)) print("sBits: %s" % repr(header.sBits)) print("height: %s" % repr(header.height)) print("size: %s" % repr(header.size)) print("timestamp: %s" % repr(header.timestamp)) print("nonce: %s" % repr(header.nonce)) print("extraData: %s" % repr(header.extraData.hex())) print("stakeVersion: %s" % repr(header.stakeVersion)) recoded = header.serialize().hex() self.assertEqual(recoded, encoded) self.assertEqual( header.id(), "52dc18bd18910e0e785411305b04f1281353ab29135a144c0fca9ea4746c2b66")
def mac(key, msg): """ SHA256-based message authentication code. Returns: ByteArray: The authentication hash. """ return ByteArray(hmacDigest(key.bytes(), msg.bytes(), hashlib.sha256))
def hashH(b): """ The BLAKE256 hash as a ByteArray. Returns: ByteArray: The hash. """ return ByteArray(blake_hash(b), length=BLAKE256_SIZE)
def hash160(b): """ A RIPEMD160 hash of the blake256 hash of the input. Returns: ByteArray: A 20-byte hash. """ h = hashlib.new("ripemd160") h.update(blake_hash(b)) return ByteArray(h.digest())
def hashFromHex(s): """ Parse a transaction hash or block hash from a hexadecimal string. Args: s (str): A byte-revesed, hexadecimal string encoded hash. Returns: ByteArray: Decoded hash """ return reversed(ByteArray(s))
def string(self): """ string returns the extended key as a base58-encoded string. See `decodeExtendedKey` for decoding. Returns: str: The encoded extended key. """ if self.key.iszero(): raise ZeroBytesError("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 b58encode(serializedBytes.bytes()).decode()
def __init__(self, pw): """ Args: pw (byte-like): A password that deterministically generates the key. """ super().__init__() salt = ByteArray(generateSeed(KEY_SIZE)) b = lambda v: ByteArray(v).bytes() func, hashName, iterations = defaultKDFParams() self.key = ByteArray( hashlib.pbkdf2_hmac(hashName, b(pw), salt.bytes(), iterations)) digest = ByteArray(hashlib.sha256(self.key.b).digest()) self.keyParams = KDFParams(salt, digest)
def string(self): """ A base-58 encoding of the pubkey hash. Returns: str: The encoded address. """ b = ByteArray(self.netID) b += self.pkHash b += checksum(b.b) x = b.int() answer = "" while x > 0: m = x % RADIX x = x // RADIX answer += ALPHABET[m] while len(answer) < len(b) * 136 // 100: answer += ALPHABET[0] # reverse return answer[::-1]
def test_accounts(self): pw = "abc".encode() am = createNewAccountManager(testSeed, bytearray(0), pw, nets.mainnet) rekey = am.acctPrivateKey(0, nets.mainnet, pw) pubFromPriv = rekey.neuter() addr1 = pubFromPriv.deriveChildAddress(5, nets.mainnet) pubKey = am.acctPublicKey(0, nets.mainnet, "") addr2 = pubKey.deriveChildAddress(5, nets.mainnet) self.assertEqual(addr1, addr2) acct = am.openAccount(0, pw) for n in range(20): acct.getNextPaymentAddress() v = 5 satoshis = v * 1e8 txid = "abcdefghijkl" vout = 2 from tinydecred.pydecred import dcrdata utxo = dcrdata.UTXO( address=None, txid=txid, vout=vout, scriptPubKey=ByteArray(0), amount=v, satoshis=satoshis, maturity=1, ) utxocount = lambda: len(list(acct.utxoscan())) acct.addUTXO(utxo) self.assertEqual(utxocount(), 1) self.assertEqual(acct.calcBalance(1).total, satoshis) self.assertEqual(acct.calcBalance(1).available, satoshis) self.assertEqual(acct.calcBalance(0).available, 0) self.assertIsNot(acct.getUTXO(txid, vout), None) self.assertIs(acct.getUTXO("", -1), None) self.assertTrue(acct.caresAboutTxid(txid)) utxos = acct.UTXOsForTXID(txid) self.assertEqual(len(utxos), 1) acct.spendUTXOs(utxos) self.assertEqual(utxocount(), 0) acct.addUTXO(utxo) self.assertEqual(utxocount(), 1) acct.spendUTXO(utxo) self.assertEqual(utxocount(), 0)
def decode(words): """ DecodeMnemonics returns the decoded value that is encoded by words. Any words that are whitespace are empty are skipped. """ _, byteMap = pgWords() decoded = [0]*len(words) idx = 0 for word in words: word = word.strip().lower() if word == "": continue if word not in byteMap: raise Exception("unknown words in mnemonic key: %s" % word) b = byteMap[word] if int(b%2) != idx%2: raise Exception("word %v is not valid at position %v, check for missing words" % (w, idx)) decoded[idx] = b // 2 idx += 1 return ByteArray(decoded[:idx])
def writeVarInt(pver, val): """ writeVarInt serializes val to w using a variable number of bytes depending on its value. """ if val < 0xfd: return ByteArray(val, length=1) # will be length 1 if val <= MaxUint16: b = ByteArray(0xfd) b += ByteArray(val, length=2).littleEndian() return b if val <= MaxUint32: b = ByteArray(0xfe) b += ByteArray(val, length=4).littleEndian() return b b = ByteArray(0xff) b += ByteArray(val, length=8).littleEndian() return b
def test_priv_keys(self): key = ByteArray( "eaf02ca348c524e6392655ba4d29603cd1a7347d9d65cfe93ce1ebffdca22694") pk = privKeyFromBytes(key) inHash = ByteArray("00010203040506070809")
cryptoKeyPubEnc=cryptoKeyPubEnc, cryptoKeyPrivEnc=cryptoKeyPrivEnc, cryptoKeyScriptEnc=cryptoKeyScriptEnc, coinTypeLegacyPubEnc=coinTypeLegacyPubEnc, coinTypeLegacyPrivEnc=coinTypeLegacyPrivEnc, coinTypeSLIP0044PubEnc=coinTypeSLIP0044PubEnc, coinTypeSLIP0044PrivEnc=coinTypeSLIP0044PrivEnc, baseAccount=baseAccount, privParams=masterKeyPriv.params(), pubParams=masterKeyPub.params(), ) manager.addAccount(zerothAccount) return manager testSeed = ByteArray( "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef").b def addressForPubkeyBytes(b, net): return crypto.newAddressPubKeyHash(crypto.hash160(b), net, crypto.STEcdsaSecp256k1).string() class TestAccounts(unittest.TestCase): @classmethod def setUpClass(cls): helpers.prepareLogger("TestTinyCrypto") # log.setLevel(0) def test_child_neuter(self): extKey = newMaster(testSeed, nets.mainnet) extKey.child(0)
def createNewAccountManager(seed, pubPassphrase, privPassphrase, chainParams): """ Create a new account manager and a set of BIP0044 keys for creating accounts. The zeroth account is created for the provided network parameters. Args: pubPassphrase (byte-like): A user-supplied password to protect the public keys. The public keys can always be generated from the private keys, but it may be convenient to perform some actions, such as address generation, without decrypting the private keys. privPassphrase (byte-like): A user-supplied password to protect the private the account private keys. chainParams (obj): Network parameters. """ # Ensure the private passphrase is not empty. if len(privPassphrase) == 0: raise Exception( "createAddressManager: private passphrase cannot be empty") # Derive the master extended key from the seed. root = newMaster(seed, chainParams) # Derive the cointype keys according to BIP0044. legacyCoinType, slip0044CoinType = coinTypes(chainParams) coinTypeLegacyKeyPriv = root.deriveCoinTypeKey(legacyCoinType) coinTypeSLIP0044KeyPriv = root.deriveCoinTypeKey(slip0044CoinType) # Derive the account key for the first account according to BIP0044. acctKeyLegacyPriv = coinTypeLegacyKeyPriv.deriveAccountKey(0) acctKeySLIP0044Priv = coinTypeSLIP0044KeyPriv.deriveAccountKey(0) # Ensure the branch keys can be derived for the provided seed according # to BIP0044. checkBranchKeys(acctKeyLegacyPriv) checkBranchKeys(acctKeySLIP0044Priv) # The address manager needs the public extended key for the account. acctKeyLegacyPub = acctKeyLegacyPriv.neuter() acctKeySLIP0044Pub = acctKeySLIP0044Priv.neuter() # Generate new master keys. These master keys are used to protect the # crypto keys that will be generated next. masterKeyPub = crypto.SecretKey(pubPassphrase) masterKeyPriv = crypto.SecretKey(privPassphrase) # Generate new crypto public, private, and script keys. These keys are # used to protect the actual public and private data such as addresses, # extended keys, and scripts. cryptoKeyPub = ByteArray(generateSeed(crypto.KEY_SIZE)) cryptoKeyPriv = ByteArray(generateSeed(crypto.KEY_SIZE)) cryptoKeyScript = ByteArray(generateSeed(crypto.KEY_SIZE)) # // Encrypt the crypto keys with the associated master keys. cryptoKeyPubEnc = masterKeyPub.encrypt(cryptoKeyPub.b) cryptoKeyPrivEnc = masterKeyPriv.encrypt(cryptoKeyPriv.b) cryptoKeyScriptEnc = masterKeyPriv.encrypt(cryptoKeyScript.b) # Encrypt the legacy cointype keys with the associated crypto keys. coinTypeLegacyKeyPub = coinTypeLegacyKeyPriv.neuter() ctpes = coinTypeLegacyKeyPub.string() coinTypeLegacyPubEnc = cryptoKeyPub.encrypt(ctpes.encode()) ctpes = coinTypeLegacyKeyPriv.string() coinTypeLegacyPrivEnc = cryptoKeyPriv.encrypt(ctpes.encode()) # Encrypt the SLIP0044 cointype keys with the associated crypto keys. coinTypeSLIP0044KeyPub = coinTypeSLIP0044KeyPriv.neuter() ctpes = coinTypeSLIP0044KeyPub.string() coinTypeSLIP0044PubEnc = cryptoKeyPub.encrypt(ctpes.encode()) ctpes = coinTypeSLIP0044KeyPriv.string() coinTypeSLIP0044PrivEnc = cryptoKeyPriv.encrypt(ctpes.encode()) # Encrypt the default account keys with the associated crypto keys. apes = acctKeyLegacyPub.string() acctPubLegacyEnc = cryptoKeyPub.encrypt(apes.encode()) apes = acctKeyLegacyPriv.string() acctPrivLegacyEnc = cryptoKeyPriv.encrypt(apes.encode()) apes = acctKeySLIP0044Pub.string() acctPubSLIP0044Enc = cryptoKeyPub.encrypt(apes.encode()) apes = acctKeySLIP0044Priv.string() acctPrivSLIP0044Enc = cryptoKeyPriv.encrypt(apes.encode()) # Save the information for the default account to the database. This # account is derived from the legacy coin type. baseAccount = Account(acctPubLegacyEnc, acctPrivLegacyEnc, DEFAULT_ACCOUNT_NAME, CoinSymbols.decred, chainParams.Name) # Save the account row for the 0th account derived from the coin type # 42 key. zerothAccount = Account(acctPubSLIP0044Enc, acctPrivSLIP0044Enc, DEFAULT_ACCOUNT_NAME, CoinSymbols.decred, chainParams.Name) # Open the account zerothAccount.open(cryptoKeyPriv) # Create the first payment address zerothAccount.generateNextPaymentAddress() # Close the account to zero the key zerothAccount.close() # ByteArray is mutable, so erase the keys. cryptoKeyPriv.zero() cryptoKeyScript.zero() log.debug("coinTypeLegacyKeyPriv: %s\n" % coinTypeLegacyKeyPriv.string()) log.debug("coinTypeSLIP0044KeyPriv: %s\n" % coinTypeSLIP0044KeyPriv.string()) log.debug("acctKeyLegacyPriv: %s\n" % acctKeyLegacyPriv.string()) log.debug("acctKeySLIP0044Priv: %s\n" % acctKeySLIP0044Priv.string()) log.debug("acctKeyLegacyPub: %s\n" % acctKeyLegacyPub.string()) log.debug("acctKeySLIP0044Pub: %s\n" % acctKeySLIP0044Pub.string()) log.debug("cryptoKeyPubEnc: %s\n" % cryptoKeyPubEnc.hex()) log.debug("cryptoKeyPrivEnc: %s\n" % cryptoKeyPrivEnc.hex()) log.debug("cryptoKeyScriptEnc: %s\n" % cryptoKeyScriptEnc.hex()) log.debug("coinTypeLegacyKeyPub: %s\n" % coinTypeLegacyKeyPub.string()) log.debug("coinTypeLegacyPubEnc: %s\n" % coinTypeLegacyPubEnc.hex()) log.debug("coinTypeLegacyPrivEnc: %s\n" % coinTypeLegacyPrivEnc.hex()) log.debug("coinTypeSLIP0044KeyPub: %s\n" % coinTypeSLIP0044KeyPub.string()) log.debug("coinTypeSLIP0044PubEnc: %s\n" % coinTypeSLIP0044PubEnc.hex()) log.debug("coinTypeSLIP0044PrivEnc: %s\n" % coinTypeSLIP0044PrivEnc.hex()) log.debug("acctPubLegacyEnc: %s\n" % acctPubLegacyEnc.hex()) log.debug("acctPrivLegacyEnc: %s\n" % acctPrivLegacyEnc.hex()) log.debug("acctPubSLIP0044Enc: %s\n" % acctPubSLIP0044Enc.hex()) log.debug("acctPrivSLIP0044Enc: %s\n" % acctPrivSLIP0044Enc.hex()) manager = AccountManager( cryptoKeyPubEnc=cryptoKeyPubEnc, cryptoKeyPrivEnc=cryptoKeyPrivEnc, cryptoKeyScriptEnc=cryptoKeyScriptEnc, coinTypeLegacyPubEnc=coinTypeLegacyPubEnc, coinTypeLegacyPrivEnc=coinTypeLegacyPrivEnc, coinTypeSLIP0044PubEnc=coinTypeSLIP0044PubEnc, coinTypeSLIP0044PrivEnc=coinTypeSLIP0044PrivEnc, baseAccount=baseAccount, privParams=masterKeyPriv.params(), pubParams=masterKeyPub.params(), ) manager.addAccount(zerothAccount) return manager
class SecretKey(object): """ SecretKey is a password-derived key that can be used for encryption and decryption. """ def __init__(self, pw): """ Args: pw (byte-like): A password that deterministically generates the key. """ super().__init__() salt = ByteArray(generateSeed(KEY_SIZE)) b = lambda v: ByteArray(v).bytes() func, hashName, iterations = defaultKDFParams() self.key = ByteArray( hashlib.pbkdf2_hmac(hashName, b(pw), salt.bytes(), iterations)) digest = ByteArray(hashlib.sha256(self.key.b).digest()) self.keyParams = KDFParams(salt, digest) def params(self): """ The key params can be stored in plain text. They must be provided to `rekey` to regenerate a key. Returns: KDFParams: The hash parameters and salt used to generate the key. """ return self.keyParams def encrypt(self, thing): """ Encrypt the input using the key. Args: thing (byte-like): The thing to encrypt. """ return self.key.encrypt(thing) def decrypt(self, thing): """ Decrypt the input using the key. Args: thing (byte-like): The thing to decrypt. """ return self.key.decrypt(thing) @staticmethod def rekey(password, kp): """ Regenerate a key using its origin key parameters, as returned by `params`. Args: kp (KDFParams): The key parameters from the original generation of the key being regenerated. Returns: SecretKey: The regenerated key. """ sk = SecretKey(b"") sk.keyParams = kp b = lambda v: ByteArray(v).bytes() func = kp.kdfFunc if func == "pbkdf2_hmac": sk.key = ByteArray( hashlib.pbkdf2_hmac(kp.hashName, b(password), b(kp.salt), kp.iterations)) else: raise Exception("unkown key derivation function") checkDigest = ByteArray(hashlib.sha256(sk.key.b).digest()) if checkDigest != kp.digest: raise PasswordError("rekey digest check failed") return sk
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. """ assert len(privVer) == 4 and len( pubVer) == 4, "Network version bytes of incorrect length" 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 # def __tojson__(self): # return { # "privVer": self.privVer, # "pubVer": self.pubVer, # "key": self.key, # "pubKey": self.pubKey, # "chainCode": self.chainCode, # "parentFP": self.parentFP, # "depth": self.depth, # "childNum": self.childNum, # "isPrivate": self.isPrivate, # } # @staticmethod # def __fromjson__(obj): # return ExtendedKey( # privVer = obj["privVer"], # pubVer = obj["pubVer"], # key = obj["key"], # pubKey = obj["pubKey"], # chainCode = obj["chainCode"], # parentFP = obj["parentFP"], # depth = obj["depth"], # childNum = obj["childNum"], # isPrivate = obj["isPrivate"], # ) def deriveCoinTypeKey(self, coinType): """ First two hardened child derivations in accordance with BIP0044. Args: coinType (int): The BIP0044 coin type. For a full list, see https://github.com/satoshilabs/slips/blob/master/slip-0044.md Returns: ExtendedKey: The coin-type key. """ 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. return purpose.child(coinType + HARDENED_KEY_START) 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), a private extended key will be derived. Otherwise, the derived extended key will be also be a public extended key. When the index is greater to or equal than 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 return ErrDeriveHardFromPublic 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. The ErrInvalidChild error will be returned 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:] # Both derived public or private keys rely on treating the left 32-byte # sequence calculated above (Il) as a 256-bit integer that must be # within the valid range for a secp256k1 private key. There is a small # chance (< 1 in 2^127) this condition will not hold, and in that case, # a child extended key can't be created for this index and the caller # should simply increment to the next index. if il.int() >= Curve.N or il.iszero(): raise ParameterRangeError( "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, account): """ 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 account > 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(account + 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 string(self): """ string returns the extended key as a base58-encoded string. See `decodeExtendedKey` for decoding. Returns: str: The encoded extended key. """ if self.key.iszero(): raise ZeroBytesError("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 b58encode(serializedBytes.bytes()).decode() def deriveChildAddress(self, i, net): """ The base-58 encoded address for the i'th child. Args: i (int): Child number. net (obj): Network parameters. """ child = self.child(i) return newAddressPubKeyHash( hash160(child.publicKey().serializeCompressed().b), net, STEcdsaSecp256k1).string() def privateKey(self): """ A PrivateKey structure that can be used for signatures. Returns: PrivateKey: The private key structure. """ return privKeyFromBytes(self.key) def publicKey(self): """ A PublicKey structure of the pubKey. Returns: PublicKey: The public key structure. """ return Curve.parsePubKey(self.pubKey)
def decodeExtendedKey(net, pw, key): """ Decode an base58 ExtendedKey using the passphrase and network parameters. Args: pw (SecretKey or ByteAray): The encryption key. key (str): Base-58 encoded extended key. Returns: ExtendedKey: The decoded key. """ decoded = ByteArray(b58decode(pw.decrypt(key.bytes()))) if len(decoded) != SERIALIZED_KEY_LENGTH + 4: raise Exception("decoded private key is wrong length") # The serialized format is: # version (4) || depth (1) || parent fingerprint (4)) || # child num (4) || chain code (32) || key data (33) || checksum (4) # Split the payload and checksum up and ensure the checksum matches. payload = decoded[:len(decoded) - 4] checkSum = decoded[len(decoded) - 4:] if checkSum != checksum(payload.b)[:4]: raise Exception("wrong checksum") # Ensure the version encoded in the payload matches the provided network. privVersion = net.HDPrivateKeyID pubVersion = net.HDPublicKeyID version = payload[:4] if version != privVersion and version != pubVersion: raise Exception("unknown version") # Deserialize the remaining payload fields. depth = payload[4:5].int() parentFP = payload[5:9] childNum = payload[9:13].int() chainCode = payload[13:45] keyData = payload[45:78] # The key data is a private key if it starts with 0x00. Serialized # compressed pubkeys either start with 0x02 or 0x03. isPrivate = keyData[0] == 0x00 if isPrivate: # Ensure the private key is valid. It must be within the range # of the order of the secp256k1 curve and not be 0. keyData = keyData[1:] # if keyNum.Cmp(secp256k1.S256().N) >= 0 || keyNum.Sign() == 0 { if keyData >= Curve.N or keyData.iszero(): raise Exception("unusable key") # Ensure the public key parses correctly and is actually on the # secp256k1 curve. Curve.publicKey(keyData.int()) return ExtendedKey( privVer=privVersion, pubVer=pubVersion, key=keyData, pubKey="", chainCode=chainCode, parentFP=parentFP, depth=depth, childNum=childNum, isPrivate=isPrivate, )
def btcEncode(self, pver): # sec := uint32(bh.Timestamp.Unix()) # return writeElements(w, bh.Version, &bh.PrevBlock, &bh.MerkleRoot, # &bh.StakeRoot, bh.VoteBits, bh.FinalState, bh.Voters, # bh.FreshStake, bh.Revocations, bh.PoolSize, bh.Bits, bh.SBits, # bh.Height, bh.Size, sec, bh.Nonce, bh.ExtraData, # bh.StakeVersion) # byte sizes int64 = 8 int32 = uint32 = 4 uint16 = 2 finalStateSize = 6 uint8 = 1 extraDataSize = 32 b = ByteArray(0, length=MaxHeaderSize) i = 0 b[i] = ByteArray(self.version, length=int32).littleEndian() i += int32 b[i] = ByteArray(self.prevBlock, length=HASH_SIZE) i += HASH_SIZE b[i] = ByteArray(self.merkleRoot, length=HASH_SIZE) i += HASH_SIZE b[i] = ByteArray(self.stakeRoot, length=HASH_SIZE) i += HASH_SIZE b[i] = ByteArray(self.voteBits, length=uint16).littleEndian() i += uint16 b[i] = ByteArray(self.finalState, length=finalStateSize) i += finalStateSize b[i] = ByteArray(self.voters, length=uint16).littleEndian() i += uint16 b[i] = ByteArray(self.freshStake, length=uint8).littleEndian() i += uint8 b[i] = ByteArray(self.revocations, length=uint8).littleEndian() i += uint8 b[i] = ByteArray(self.poolSize, length=uint32).littleEndian() i += uint32 b[i] = ByteArray(self.bits, length=uint32).littleEndian() i += uint32 b[i] = ByteArray(self.sBits, length=int64).littleEndian() i += int64 b[i] = ByteArray(self.height, length=uint32).littleEndian() i += uint32 b[i] = ByteArray(self.size, length=uint32).littleEndian() i += uint32 b[i] = ByteArray(self.timestamp, length=uint32).littleEndian() i += uint32 b[i] = ByteArray(self.nonce, length=uint32).littleEndian() i += uint32 b[i] = ByteArray(self.extraData, length=extraDataSize) i += extraDataSize b[i] = ByteArray(self.stakeVersion, length=uint32).littleEndian() i += uint32 if i != MaxHeaderSize: raise Exception("unexpected BlockHeader enocoded size") return b
def acctPublicKey(self, acct, net, pw): userSecret = crypto.SecretKey.rekey(pw, self.pubParams) cryptKeyPub = ByteArray( userSecret.decrypt(self.cryptoKeyPubEnc.bytes())) account = self.accounts[acct] return account.publicExtendedKey(cryptKeyPub)
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), a private extended key will be derived. Otherwise, the derived extended key will be also be a public extended key. When the index is greater to or equal than 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 return ErrDeriveHardFromPublic 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. The ErrInvalidChild error will be returned 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:] # Both derived public or private keys rely on treating the left 32-byte # sequence calculated above (Il) as a 256-bit integer that must be # within the valid range for a secp256k1 private key. There is a small # chance (< 1 in 2^127) this condition will not hold, and in that case, # a child extended key can't be created for this index and the caller # should simply increment to the next index. if il.int() >= Curve.N or il.iszero(): raise ParameterRangeError( "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 acctPrivateKey(self, acct, net, pw): userSecret = crypto.SecretKey.rekey(pw, self.privParams) cryptKeyPriv = ByteArray( userSecret.decrypt(self.cryptoKeyPrivEnc.bytes())) account = self.accounts[acct] return account.privateExtendedKey(cryptKeyPriv)