def getIndex( mnemonic: str, password: str, skip: int ) -> int: seed: bytes = sha256(Bip39SeedGenerator(mnemonic).Generate(password)).digest() c: int = -1 failures: int = 0 while skip != -1: c += 1 try: BIP32.derive( seed, [44 + (1 << 31), 5132 + (1 << 31), 0 + (1 << 31), 0, c] ) #Since we derived a valid address, decrement skip. skip -= 1 failures = 0 except Exception: #Safety check to prevent infinite execution. failures += 1 if failures == 100: raise Exception("Invalid mnemonic passed to getPrivateKey.") continue return c
def getMnemonic( password: str = "" ) -> str: while True: res: str = Bip39MnemonicGenerator.FromWordsNumber(Bip39WordsNum.WORDS_NUM_24) seed: bytes = sha256(Bip39SeedGenerator(res).Generate(password)).digest() try: BIP32.derive(seed, [44 + (1 << 31), 5132 + (1 << 31), 0 + (1 << 31), 0]) BIP32.derive(seed, [44 + (1 << 31), 5132 + (1 << 31), 0 + (1 << 31), 1]) except Exception: continue return res
def getChangePublicKey( mnemonic: str, password: str, skip: int ) -> bytes: seed: bytes = sha256(Bip39SeedGenerator(mnemonic).Generate(password)).digest() extendedKey: bytes = bytes() #Above's getIndex, yet utilizing the return value of derive c: int = -1 failures: int = 0 while skip != -1: c += 1 try: extendedKey = BIP32.derive( seed, [44 + (1 << 31), 5132 + (1 << 31), 0 + (1 << 31), 1, c] ) #Since we derived a valid address, decrement skip. skip -= 1 failures = 0 except Exception: #Safety check to prevent infinite execution. failures += 1 if failures == 100: raise Exception("Invalid mnemonic passed to getPrivateKey.") continue return RistrettoScalar(extendedKey[:32]).toPoint().serialize()
def getPrivateKey( mnemonic: str, password: str, skip: int ) -> bytes: seed: bytes = sha256(Bip39SeedGenerator(mnemonic).Generate(password)).digest() return BIP32.derive( seed, [44 + (1 << 31), 5132 + (1 << 31), 0 + (1 << 31), 0, getIndex(mnemonic, password, skip)] )
def DerivationTest(rpc: RPC) -> None: #Start by testing BIP 32, 39, and 44 functionality in general. for _ in range(10): rpc.call("personal", "setWallet") verifyMnemonicAndAccount(rpc) #Set specific Mnemonics and ensure they're handled properly. for _ in range(10): mnemonic: str = getMnemonic() rpc.call("personal", "setWallet", {"mnemonic": mnemonic}) verifyMnemonicAndAccount(rpc, mnemonic) #Create Mnemonics with passwords and ensure they're handled properly. for _ in range(10): password: str = os.urandom(32).hex() rpc.call("personal", "setWallet", {"password": password}) verifyMnemonicAndAccount(rpc, password=password) #Set specific Mnemonics with passwords and ensure they're handled properly. for i in range(10): password: str = os.urandom(32).hex() #Non-hex string. if i == 0: password = "******" mnemonic: str = getMnemonic(password) rpc.call("personal", "setWallet", { "mnemonic": mnemonic, "password": password }) verifyMnemonicAndAccount(rpc, mnemonic, password) #setWallet, getMnemonic, getMeritHolderKey, getMeritHolderNick's non-existent case, and getAccount have now been tested. #This leaves getAddress with specific indexes. #Clear the Wallet. rpc.call("personal", "setWallet") #Start by testing specific derivation. password: str = "password since it shouldn't be relevant" for _ in range(10): mnemonic: str = getMnemonic(password) index: int = 100 key: bytes while True: try: key = BIP32.derive( sha256(Bip39SeedGenerator(mnemonic).Generate( password)).digest(), [ 44 + (1 << 31), 5132 + (1 << 31), 0 + (1 << 31), 0, index ]) break except Exception: index += 1 rpc.call("personal", "setWallet", { "mnemonic": mnemonic, "password": password }) addr: str = bech32_encode( "mr", convertbits( bytes([0]) + RistrettoScalar(key[:32]).toPoint().serialize(), 8, 5)) if rpc.call("personal", "getAddress", {"index": index}) != addr: raise TestError("Didn't get the correct address for this index.") #Test if a specific address is requested, it won't come up naturally. #This isn't explicitly required by the RPC spec, which has been worded carefully to leave this open ended. #The only requirement is the address was never funded and the index is sequential (no moving backwards). #The node offers this feature to try to make mixing implicit/explicit addresses safer, along with some internal benefits. #That said, said internal benefits are minimal or questionable, hence why the RPC docs are open ended. #This way we can decide differently in the future. rpc.call("personal", "setWallet") firstAddr: str = rpc.call("personal", "getAddress") #Explicitly get the first address. for i in range(256): try: rpc.call("personal", "getAddress", {"index": i}) break except TestError: if i == 255: raise Exception( "The first 256 address were invalid; this should be practically impossible." ) if firstAddr == rpc.call("personal", "getAddress"): raise TestError("Explicitly grabbed address was naturally returned.") #Test error cases. #Mnemonic with an improper amount of entropy. #Runs multiple times in case the below error pops up for the sole reason the Mnemonic didn't have viable keys. #This should error earlier than that though. for _ in range(16): try: rpc.call( "personal", "setWallet", { "mnemonic": Bip39MnemonicGenerator.FromWordsNumber( Bip39WordsNum.WORDS_NUM_12) }) raise Exception() except Exception as e: if str(e) != "-3 Invalid mnemonic or password.": raise TestError( "Could set a Mnemonic with too little entropy.") #Mnemonic with additional spaces. rpc.call("personal", "setWallet") mnemonic: str = rpc.call("personal", "getMnemonic") rpc.call("personal", "setWallet", {"mnemonic": " " + (" " * 2).join(mnemonic.split(" ")) + " "}) if rpc.call("personal", "getMnemonic") != mnemonic: raise TestError( "Meros didn't handle a mnemonic with extra whitespace.") #Negative index to getAddress. try: rpc.call("personal", "getAddress", {"index": -1}) raise Exception() except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError("Could call getAddress with a negative index.")