def test_crypto_sign_seed_keypair(self): seed = pysodium.crypto_generichash( b'howdy', outlen=pysodium.crypto_sign_SEEDBYTES) pk, sk = pysodium.crypto_sign_seed_keypair(seed) pk2, sk2 = pysodium.crypto_sign_seed_keypair(seed) self.assertEqual(pk, pk2) self.assertEqual(sk, sk2)
def test_s4(): ilk, iuk = crypto_sign_seed_keypair(rng.randombytes(KEY_BYTES)) piuk = [gen_iuk(), gen_iuk(), gen_iuk()] iuk = iuk[:KEY_BYTES] rb = Rescue.seal(iuk, rc, 9, 1, .6) imk = enhash(iuk) ab = Access().seal(imk + ilk, pw) pb = Previous.seal(imk, piuk) sitepw = genpasswd() sb = Secret.make(b'shop', b'amazon', b'*****@*****.**').seal(imk, sitepw) s = SQRLdata([ab, rb, pb, sb]) sa = s.ascii() print(sa) source = io.BytesIO(sa.encode('ascii')) ab1, rb1, pb1, sb1 = SQRLdata.load(source, tm) print(ab1) print(rb1) print(pb1) print(sb1) iuk1 = rb1.open(rc) assert iuk == iuk1 imk1, ilk1 = ab1.open(pw) assert ilk == ilk1 assert imk == imk1 piuk1 = pb1.open(imk) assert piuk == piuk1
def from_secret_key(cls, secret_key: bytes, curve=b'ed'): """ Creates a key object from a secret exponent. :param secret_key: secret exponent or seed :param curve: an elliptic curve used, default is ed25519 """ # Ed25519 if curve == b'ed': # Dealing with secret key or seed? if len(secret_key) == 64: public_key = pysodium.crypto_sign_sk_to_pk(sk=secret_key) else: public_key, secret_key = pysodium.crypto_sign_seed_keypair(seed=secret_key) # Secp256k1 elif curve == b'sp': sk = secp256k1.PrivateKey(secret_key) public_key = sk.pubkey.serialize() # P256 elif curve == b'p2': pk = get_public_key(bytes_to_int(secret_key), curve=P256) public_key = SEC1Encoder.encode_public_key(pk) else: assert False return cls(public_key, secret_key, curve=curve)
def test_sign_verify(footer: bytes) -> None: """Check that verify() reverses sign().""" public_key, secret_key = crypto_sign_seed_keypair(b"\x00" * crypto_sign_SEEDBYTES) message = b"foo" signed = version2.sign(message, secret_key, footer) assert version2.verify(signed, public_key, footer) == message
def from_secret_exponent(cls, secret_exponent: bytes, curve=b'ed', activation_code=None): """ Creates a key object from a secret exponent. :param secret_exponent: secret exponent or seed :param curve: b'sp' for Secp251k1, b'p2' for P256/Secp256r1, b'ed' for Ed25519 (default) :param activation_code: secret for initializing account balance """ # Ed25519 if curve == b'ed': # Dealing with secret exponent or seed? if len(secret_exponent) == 64: public_point = pysodium.crypto_sign_sk_to_pk( sk=secret_exponent) else: public_point, secret_exponent = pysodium.crypto_sign_seed_keypair( seed=secret_exponent) # Secp256k1 elif curve == b'sp': sk = secp256k1.PrivateKey(secret_exponent) public_point = sk.pubkey.serialize() # P256 elif curve == b'p2': pk = get_public_key(bytes_to_int(secret_exponent), curve=P256) public_point = SEC1Encoder.encode_public_key(pk) else: assert False return cls(public_point, secret_exponent, curve=curve, activation_code=activation_code)
def get_keys(mnemonic, email, password): salt = unicodedata.normalize("NFKD", (email + password)) seed = m.to_seed(mnemonic.encode('utf-8'), salt.encode('utf-8')) pk, sk = pysodium.crypto_sign_seed_keypair(seed[0:32]) pkh = blake2b(pk, 20).digest() pkhb58 = base58_encode(pkh, prefix=b'tz1').decode('utf-8') return (sk, pk, pkh, pkhb58)
def get_keys(mnemonic, email, password): salt = unicodedata.normalize( "NFKD", (email + password).decode("utf8")).encode("utf8") seed = bitcoin.mnemonic_to_seed(mnemonic, salt) pk, sk = pysodium.crypto_sign_seed_keypair(seed[0:32]) pkh = blake2b(pk, 20).digest() pkhb58 = bitcoin.bin_to_b58check(pkh, magicbyte=434591) return (sk, pk, pkh, pkhb58)
def get_signkey(id, rwd): mk = get_masterkey() seed = pysodium.crypto_generichash(SIGN_CTX, mk) clearmem(mk) # rehash with rwd so the user always contributes his pwd and the sphinx server it's seed seed = pysodium.crypto_generichash(seed, id) if rwd_keys: seed = pysodium.crypto_generichash(seed, rwd) pk, sk = pysodium.crypto_sign_seed_keypair(seed) clearmem(seed) return sk, pk
def derive_keypair(salt, flag): flag = flag.encode('utf-8') assert isinstance(salt, bytes) assert isinstance(flag, bytes) assert len(salt) == pysodium.crypto_pwhash_scryptsalsa208sha256_SALTBYTES chall_seed = pysodium.crypto_pwhash_scryptsalsa208sha256( pysodium.crypto_sign_SEEDBYTES, flag, salt, Settings.scrypt_ops_limit, Settings.scrypt_mem_limit) return pysodium.crypto_sign_seed_keypair(chall_seed)
def derive_keypair(salt, opslimit, memlimit, flag): flag = flag.encode('utf-8') assert isinstance(salt, bytes) assert isinstance(flag, bytes) assert len(salt) == pysodium.crypto_pwhash_SALTBYTES chall_seed = pysodium.crypto_pwhash(pysodium.crypto_sign_SEEDBYTES, flag, salt, opslimit, memlimit, pysodium.crypto_pwhash_ALG_ARGON2ID13) return pysodium.crypto_sign_seed_keypair(chall_seed)
def key_pair(seed: Optional[bytes] = None) -> Tuple[bytes, bytes]: """A new public key and secret key pair. :param seed: Seed value. Must be at least 32 characters in length """ if seed: if len(seed) < crypto_sign_SEEDBYTES: message = "'seed' argument must be of length > {}" raise ValueError(message.format(crypto_sign_SEEDBYTES)) return crypto_sign_seed_keypair(seed) return crypto_sign_keypair()
def encrypt_keys(self, keyidxs, keys, topic, msgval=None): if (isinstance(topic,(bytes,bytearray))): self._logger.debug("passed a topic in bytes (should be string)") topic = topic.decode('utf-8') # # msgval should be a msgpacked chain. # The public key in the array is the public key to send the key to, using a # common DH-derived key between that public key and our private encryption key. # Then there is at least one more additional item, random bytes: # (3) random bytes # Currently items after this are ignored, and reserved for future use. # try: with self.__allowdenylist_lock: pk,_ = process_chain(msgval,topic,'key-encrypt-request',allowlist=self.__allowlist,denylist=self.__denylist) # Construct shared secret as sha256(topic || random0 || random1 || our_private*their_public) epk = self.__cryptokey.get_epk(topic, 'encrypt_keys') pks = [pk[2]] eks = self.__cryptokey.use_epk(topic, 'encrypt_keys',pks) ek = eks[0] eks[0] = epk random0 = pk[3] random1 = pysodium.randombytes(self.__randombytes) ss = pysodium.crypto_hash_sha256(topic.encode('utf-8') + random0 + random1 + ek)[0:pysodium.crypto_secretbox_KEYBYTES] nonce = pysodium.randombytes(pysodium.crypto_secretbox_NONCEBYTES) # encrypt keys and key indexes (MAC appended, nonce prepended) msg = [] for i in range(0,len(keyidxs)): msg.append(keyidxs[i]) msg.append(keys[i]) msg = msgpack.packb(msg, use_bin_type=True) msg = nonce + pysodium.crypto_secretbox(msg,nonce,ss) # this is then put in a msgpack array with the appropriate max_age, poison, and public key(s) poison = msgpack.packb([['topics',[topic]],['usages',['key-encrypt']]], use_bin_type=True) msg = msgpack.packb([time()+self.__maxage,poison,eks,[random0,random1],msg], use_bin_type=True) # and signed with our signing key msg = self.__cryptokey.sign_spk(msg) # and finally put as last member of a msgpacked array chaining to ROT with self.__spk_chain_lock: tchain = self.__spk_chain.copy() if (len(tchain) == 0): poison = msgpack.packb([['topics',[topic]],['usages',['key-encrypt']],['pathlen',1]], use_bin_type=True) lastcert = msgpack.packb([time()+self.__maxage,poison,self.__cryptokey.get_spk()], use_bin_type=True) _,tempsk = pysodium.crypto_sign_seed_keypair(unhexlify(b'4c194f7de97c67626cc43fbdaf93dffbc4735352b37370072697d44254e1bc6c')) tchain.append(pysodium.crypto_sign(lastcert,tempsk)) provision = msgpack.packb([msgpack.packb([0,b'\x90',self.__cryptokey.get_spk()]),self.__cryptokey.sign_spk(lastcert)], use_bin_type=True) self._logger.warning("Current signing chain is empty. Use %s to provision access and then remove temporary root of trust from allowedlist.", provision.hex()) tchain.append(msg) msg = msgpack.packb(tchain, use_bin_type=True) except Exception as e: self._logger.warning("".join(format_exception_shim(e))) return None return msg
def __init__(self, password): if (isinstance(password,(str,))): password = password.encode('utf-8') self._salt = pysodium.crypto_hash_sha256(b'Root of Trust' + password)[0:pysodium.crypto_pwhash_scryptsalsa208sha256_SALTBYTES] print("") print("Deriving root key with:") print(" opsl = ", self._ops) print(" meml = ", self._mem) print(" salt = ", hexlify(self._salt)) self._seed = pysodium.crypto_pwhash_scryptsalsa208sha256(pysodium.crypto_sign_SEEDBYTES, password, self._salt, opslimit=self._ops, memlimit=self._mem) self._pk, self._sk = pysodium.crypto_sign_seed_keypair(self._seed) print(" Root Public Key: ", hexlify(self._pk)) print("")
def create_keypair(seed: bytes = None) -> (bytes, bytes): """ Create a public and private signing keypair from a seed value. Args: seed: Seed for keypair Returns: A tuple of (public key, secret key) """ if not seed: seed = random_seed() pk, sk = pysodium.crypto_sign_seed_keypair(seed) return pk, sk
def from_mnemonic(cls, mnemonic, passphrase='', email='', validate=True): """ Creates a key object from a bip39 mnemonic. :param mnemonic: a 15 word bip39 english mnemonic :param passphrase: a mnemonic password or a fundraiser key :param email: email used if a fundraiser key is passed :param validate: whether to check mnemonic or not """ if isinstance(mnemonic, list): mnemonic = ' '.join(mnemonic) if validate: validate_mnemonic(mnemonic) seed = Mnemonic.to_seed(mnemonic, passphrase=email + passphrase) public_key, secret_key = pysodium.crypto_sign_seed_keypair(seed=seed[:32]) return cls(public_key, secret_key)
def check(address, mnemonic, email, password): salt = unicodedata.normalize("NFKD", (email + password)).encode("utf8") try: seed = bitcoin.mnemonic_to_seed(mnemonic.encode(), salt) except: return -1 pk, sk = pysodium.crypto_sign_seed_keypair(seed[0:32]) pkh = blake2b(pk, 20).digest() decrypted_address = tezos_pkh(pkh) if address == decrypted_address: return 1 else: return 0
def doesGeneratedMatchOriginal(myMnemonic, myPassword, myEmail, myAddress): try: seed = mnemonic_and_passphraase_to_seed(myMnemonic, myPassword, myEmail) except: print('bad') sys.exit(1) pk, sk = pysodium.crypto_sign_seed_keypair(seed[0:32]) pkh = hashlib.blake2b(pk, digest_size=20).digest() prefix = {b'ed': b'tz1', b'sp': b'tz2', b'p2': b'tz3'}[b"ed"] test = base58_encode(pkh, prefix).decode() if myAddress == test: # print("Found the right seed words!") # print("Address: " + myAddress) # print(myMnemonic) return True return False
def make_dummy_wallets(n, blind): # Not a realistic shape, but for an alphanet faucet it's better to # have less variance. amounts = np.random.pareto(10.0, n) amounts = amounts / sum(amounts) * 760e6 wallets = {} secret_seeds = {} for i in range(0, n): seed = blake2b(str(i), 32, key=blind).digest() pk, sk = pysodium.crypto_sign_seed_keypair(seed) pkh = blake2b(pk, 20).digest() pkh_b58 = bitcoin.bin_to_b58check(pkh, magicbyte=434591) amount = tez_to_int(amounts[i]) wallets[pkh_b58] = amount secret = secret_code(pkh, blind) secret_seeds[pkh_b58] = (bitcoin.bin_to_b58check(seed, magicbyte=219101703), amount, binascii.hexlify(secret)) return wallets, secret_seeds
def _create_asymmetric_key( version: int, raw_public_key_material: bytes = b"", raw_secret_key_material: bytes = b"", ) -> Tuple[bytes, bytes]: """Return new public and secret keys.""" _validate_version(version) if not raw_public_key_material or not raw_secret_key_material: ( raw_public_key_material, raw_secret_key_material, ) = pysodium.crypto_sign_seed_keypair( os.urandom(pysodium.crypto_sign_SEEDBYTES)) public_key: bytes = _serialize_key(version, _TYPE_PUBLIC, raw_public_key_material) secret_key: bytes = _serialize_key(version, _TYPE_SECRET, raw_secret_key_material) return public_key, secret_key
def from_mnemonic(cls, mnemonic: Union[List[str], str], passphrase: str = '', email: str = '', validate: bool = True, curve: bytes = b'ed', activation_code: Optional[str] = None, language: str = DEFAULT_LANGUAGE) -> 'Key': """Creates a key object from a bip39 mnemonic. :param mnemonic: a 15 word bip39 english mnemonic :param passphrase: a mnemonic password or a fundraiser key :param email: email used if a fundraiser key is passed :param validate: whether to check mnemonic or not :param curve: b'sp' for secp251k1, b'p2' for P256/secp256r1, b'ed' for Ed25519 (default) :param activation_code: secret for initializing account balance :param language: The English label for the language of the mnemonic. This is needed for validation :rtype: Key """ if isinstance(mnemonic, list): mnemonic = ' '.join(mnemonic) if validate: validate_mnemonic(mnemonic, language=language) seed = Mnemonic.to_seed(mnemonic, passphrase=email + passphrase) if curve == b'ed': _, secret_exponent = pysodium.crypto_sign_seed_keypair( seed=seed[:32]) elif curve == b'sp': secret_exponent = seed[:32] elif curve == b'p2': secret_exponent = seed[:32] else: assert False return cls.from_secret_exponent(secret_exponent, curve=curve, activation_code=activation_code)
def check(password, email, mnemonic, address, iters=2048): salt = unicodedata.normalize("NFKD", (email + password)).encode("utf8") seed = pbkdf2_hmac(hash_name='sha512', password=mnemonic, salt=b"mnemonic" + salt, iterations=iters) pk, sk = pysodium.crypto_sign_seed_keypair(seed[0:32]) pkh = blake2b(pk, 20).digest() decrypted_address = bitcoin.bin_to_b58check(pkh, magicbyte=434591) if address == decrypted_address: found_it = "True" print("found it ") print("Your password is: ", password) with open("password.lst", 'a') as z: z.write((password) + '\n') z.close() else: found_it = "False" return (found_it, password)
def from_mnemonic(cls, mnemonic, passphrase='', email='', validate=True, curve=b'ed', activation_code=None): """ Creates a key object from a bip39 mnemonic. :param mnemonic: a 15 word bip39 english mnemonic :param passphrase: a mnemonic password or a fundraiser key :param email: email used if a fundraiser key is passed :param validate: whether to check mnemonic or not :param curve: b'sp' for secp251k1, b'p2' for P256/secp256r1, b'ed' for Ed25519 (default) :param activation_code: secret for initializing account balance :rtype: Key """ if isinstance(mnemonic, list): mnemonic = ' '.join(mnemonic) if validate: validate_mnemonic(mnemonic) seed = Mnemonic.to_seed(mnemonic, passphrase=email + passphrase) if curve == b'ed': _, secret_exponent = pysodium.crypto_sign_seed_keypair( seed=seed[:32]) elif curve == b'sp': secret_exponent = seed[:32] elif curve == b'p2': secret_exponent = seed[:32] else: assert False return cls.from_secret_exponent(secret_exponent, curve=curve, activation_code=activation_code)
def signed_epk(self, topic, epk=None): if (isinstance(topic,(bytes,bytearray))): self._logger.debug("passed a topic in bytes (should be string)") topic = topic.decode('utf-8') # # returns the public key of a current or given ephemeral key for the specified topic # (generating a new one if not present), signed by our signing key, # with a fresh random value, and with the chain to the ROT prepended. # try: if epk is None: epk = self.__cryptokey.get_epk(topic,'decrypt_keys') random0 = pysodium.randombytes(self.__randombytes) # we allow either direct-to-producer or via-controller key establishment poison = msgpack.packb([['topics',[topic]],['usages',['key-encrypt-request','key-encrypt-subscribe']]], use_bin_type=True) msg = msgpack.packb([time()+self.__maxage,poison,epk,random0], use_bin_type=True) # and signed with our signing key msg = self.__cryptokey.sign_spk(msg) # and finally put as last member of a msgpacked array chaining to ROT with self.__spk_chain_lock: tchain = self.__spk_chain.copy() if (len(tchain) == 0): # Use default for direct use when empty. self.__spk_direct_request = True poison = msgpack.packb([['topics',[topic]],['usages',['key-encrypt-request','key-encrypt-subscribe']],['pathlen',1]], use_bin_type=True) lastcert = msgpack.packb([time()+self.__maxage,poison,self.__cryptokey.get_spk()], use_bin_type=True) _,tempsk = pysodium.crypto_sign_seed_keypair(unhexlify(b'4c194f7de97c67626cc43fbdaf93dffbc4735352b37370072697d44254e1bc6c')) tchain.append(pysodium.crypto_sign(lastcert,tempsk)) provision = msgpack.packb([msgpack.packb([0,b'\x90',self.__cryptokey.get_spk()]),self.__cryptokey.sign_spk(lastcert)], use_bin_type=True) self._logger.warning("Current signing chain is empty. Use %s to provision access and then remove temporary root of trust from allowedlist.", provision.hex()) tchain.append(msg) msg = msgpack.packb(tchain, use_bin_type=True) return msg except Exception as e: self._logger.warning("".join(format_exception_shim(e))) pass return None
def createid(fname): '''create a new SQRL identity, storing it to `fname`''' click.echo('creating new id in "{}"'.format(fname)) pw = getnewpassword('access password') print(type(pw), pw) rc = rescue_code() click.echo( 'Here is your emergency rescue code (write it in a secure place or memorize it):' ) urc = rc.decode('ascii') click.echo(' '.join(urc[i:i + 4] for i in range(0, len(urc), 4))) ilk, iuk = crypto_sign_seed_keypair(rng.randombytes(KEY_BYTES)) iuk = iuk[:KEY_BYTES] imk = enhash(iuk) click.echo( 'Encrypting your new identity. (This should take about 60 seconds.)') ab = Access().seal(imk + ilk, pw) rb = Rescue.seal(iuk, rc) sd = SQRLdata([ab, rb]) with open(fname, 'wb') as fo: sd.dump(fo) click.echo('Your new identity is now stored in "{}"'.format( os.path.abspath(fname)))
def __init__(self, password, rot): if (isinstance(password,(str,))): password = password.encode('utf-8') try: rot = unhexlify(rot) except: pass self._salt = {} self._salt['producer'] = pysodium.crypto_hash_sha256(b'producer' + rot)[0:pysodium.crypto_pwhash_scryptsalsa208sha256_SALTBYTES] self._salt['consumer'] = pysodium.crypto_hash_sha256(b'consumer' + rot)[0:pysodium.crypto_pwhash_scryptsalsa208sha256_SALTBYTES] self._salt['prodcon'] = pysodium.crypto_hash_sha256(b'prodcon' + rot)[0:pysodium.crypto_pwhash_scryptsalsa208sha256_SALTBYTES] self._seed = {} self._pk = {} self._sk = {} print("") print("Deriving provisioning keys with:") print(" opsl = ", self._ops) print(" meml = ", self._mem) for key in self._salt.keys(): print(" salt (", key, ") = ", hexlify(self._salt[key])) self._seed[key] = pysodium.crypto_pwhash_scryptsalsa208sha256(pysodium.crypto_sign_SEEDBYTES, password, self._salt[key], opslimit=self._ops, memlimit=self._mem) self._pk[key], self._sk[key] = pysodium.crypto_sign_seed_keypair(self._seed[key]) print(" Signing Public Key (", key, "): ", hexlify(self._pk[key])) print("")
def test_crypto_sign_sk_to_seed(self): seed1 = pysodium.crypto_generichash(b'howdy', outlen=pysodium.crypto_sign_SEEDBYTES) _, sk = pysodium.crypto_sign_seed_keypair(seed1) seed2 = pysodium.crypto_sign_sk_to_seed(sk) self.assertEqual(seed1, seed2)
def gen_iuk(): return crypto_sign_seed_keypair(rng.randombytes(KEY_BYTES))[1][:KEY_BYTES]
if __name__ == '__main__': if len(sys.argv) == 18: mnemonic = ' '.join(sys.argv[1:16]).lower() email = sys.argv[16] password = sys.argv[17] salt = unicodedata.normalize( "NFKD", (email + password).decode("utf8")).encode("utf8") try: seed = bitcoin.mnemonic_to_seed(mnemonic, salt) except: print("Invalid mnemonic") exit(1) pk, sk = pysodium.crypto_sign_seed_keypair(seed[0:32]) pkh = blake2b(pk, 20).digest() print "public key hash: ", tezos_pkh(pkh) elif len(sys.argv) == 2: tz_input = sys.argv[1] assert (tz_input == bitcoin.bin_to_b58check( bitcoin.b58check_to_bin(tz_input)[2:], magicbyte=434591)) try: pkh = bitcoin.b58check_to_bin(tz_input)[2:] except: print "Invalid public key hash" else:
def test_crypto_sign_sk_to_seed(self): seed1 = pysodium.crypto_generichash( b'howdy', outlen=pysodium.crypto_sign_SEEDBYTES) _, sk = pysodium.crypto_sign_seed_keypair(seed1) seed2 = pysodium.crypto_sign_sk_to_seed(sk) self.assertEqual(seed1, seed2)
def test_pysodium(): """ Test all the functions needed from pysodium libarary (libsodium) """ # crypto_sign signatures with Ed25519 keys # create keypair without seed verkey, sigkey = pysodium.crypto_sign_keypair() assert len(verkey) == 32 == pysodium.crypto_sign_PUBLICKEYBYTES assert len(sigkey) == 64 == pysodium.crypto_sign_SECRETKEYBYTES assert 32 == pysodium.crypto_sign_SEEDBYTES sigseed = pysodium.randombytes(pysodium.crypto_sign_SEEDBYTES) assert len(sigseed) == 32 # seed = (b'J\xeb\x06\xf2BA\xd6/T\xe1\xe2\xe2\x838\x8a\x99L\xd9\xb5(\\I\xccRb\xc8\xd5\xc7Y\x1b\xb6\xf0') # Ann's seed sigseed = ( b'PTi\x15\xd5\xd3`\xf1u\x15}^r\x9bfH\x02l\xc6\x1b\x1d\x1c\x0b9\xd7{\xc0_\xf2K\x93`' ) assert len(sigseed) == 32 # try key stretching from 16 bytes using pysodium.crypto_pwhash() assert 16 == pysodium.crypto_pwhash_SALTBYTES salt = pysodium.randombytes(pysodium.crypto_pwhash_SALTBYTES) assert len(salt) == 16 # salt = b'\x19?\xfa\xc7\x8f\x8b\x7f\x8b\xdbS"$\xd7[\x85\x87' # algorithm default is argon2id sigseed = pysodium.crypto_pwhash( outlen=32, passwd="", salt=salt, opslimit=pysodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, memlimit=pysodium.crypto_pwhash_MEMLIMIT_INTERACTIVE, alg=pysodium.crypto_pwhash_ALG_DEFAULT) assert len(sigseed) == 32 # seed = (b'\xa9p\x89\x7f+\x0e\xc4\x9c\xf2\x01r\xafTI\xc0\xfa\xac\xd5\x99\xf8O\x8f=\x843\xa2\xb6e\x9fO\xff\xd0') # creates signing/verification key pair from seed verkey, sigkey = pysodium.crypto_sign_seed_keypair(sigseed) assert len(verkey) == 32 assert len(sigkey) == 64 # sigkey is seed and verkey concatenated. Libsodium does this as an optimization # because the signing scheme needs both the private key (seed) and the public key so # instead of recomputing the public key each time from the secret key it requires # the public key as an input of and instead of two separate inputs, one for the # secret key and one for the public key, it uses a concatenated form. # Essentially crypto_sign_seed_keypair and crypto_sign_keypair return redundant # information in the duple (verkey, sigkey) because sigkey includes verkey # so one could just store sigkey and extract verkey or sigseed when needed # or one could just store verkey and sigseed and reconstruct sigkey when needed. # crypto_sign_detached requires sigkey (sigseed + verkey) # crypto_sign_verify_detached reqires verkey only # https://crypto.stackexchange.com/questions/54353/why-are-nacl-secret-keys-64-bytes-for-signing-but-32-bytes-for-box assert sigseed == sigkey[:32] assert verkey == sigkey[32:] assert sigkey == sigseed + verkey # vk = (b'B\xdd\xbb}8V\xa0\xd6lk\xcf\x15\xad9\x1e\xa7\xa1\xfe\xe0p<\xb6\xbex\xb0s\x8d\xd6\xf5\xa5\xe8Q') # utility function to extract seed from secret sigkey (really just extracting from front half) assert sigseed == pysodium.crypto_sign_sk_to_seed(sigkey) assert 64 == pysodium.crypto_sign_BYTES msg = "The lazy dog jumped over the river" msgb = msg.encode( "utf-8") # must convert unicode string to bytes in order to sign it assert msgb == b'The lazy dog jumped over the river' sig = pysodium.crypto_sign_detached(msgb, sigseed + verkey) # sigkey = seed + verkey assert len(sig) == 64 """ sig = (b"\x99\xd2<9$$0\x9fk\xfb\x18\xa0\x8c@r\x122.k\xb2\xc7\x1fp\x0e'm\x8f@" b'\xaa\xa5\x8c\xc8n\x85\xc8!\xf6q\x91p\xa9\xec\xcf\x92\xaf)\xde\xca' b'\xfc\x7f~\xd7o|\x17\x82\x1d\xd4<o"\x81&\t') """ #siga = pysodium.crypto_sign(msg.encode("utf-8"), sk)[:pysodium.crypto_sign_BYTES] #assert len(siga) == 64 #assert sig == siga try: # verify returns None if valid else raises ValueError result = pysodium.crypto_sign_verify_detached(sig, msgb, verkey) except Exception as ex: assert False assert not result assert result is None sigbad = sig[:-1] sigbad += b'A' try: # verify returns None if valid else raises ValueError result = pysodium.crypto_sign_verify_detached(sigbad, msgb, verkey) except Exception as ex: assert True assert isinstance(ex, ValueError) # crypto_box authentication encryption with X25519 keys apubkey, aprikey = pysodium.crypto_box_keypair() assert len(apubkey) == 32 == pysodium.crypto_box_SECRETKEYBYTES assert len(aprikey) == 32 == pysodium.crypto_box_PUBLICKEYBYTES repubkey = pysodium.crypto_scalarmult_curve25519_base(aprikey) assert repubkey == apubkey assert 32 == pysodium.crypto_box_SEEDBYTES boxseed = pysodium.randombytes(pysodium.crypto_box_SEEDBYTES) assert len(boxseed) == 32 bpubkey, bprikey = pysodium.crypto_box_seed_keypair(boxseed) assert len(bpubkey) == 32 assert len(bprikey) == 32 repubkey = pysodium.crypto_scalarmult_curve25519_base(bprikey) assert repubkey == bpubkey assert 24 == pysodium.crypto_box_NONCEBYTES nonce = pysodium.randombytes(pysodium.crypto_box_NONCEBYTES) assert len(nonce) == 24 # nonce = b'\x11\xfbi<\xf2\xb6k\xa05\x0c\xf9\x86t\x07\x8e\xab\x8a\x97nG\xe8\x87,\x94' atob_tx = "Hi Bob I'm Alice" atob_txb = atob_tx.encode("utf-8") # Detached recomputes shared key every time. # A encrypt to B acrypt, amac = pysodium.crypto_box_detached(atob_txb, nonce, bpubkey, aprikey) amacl = pysodium.crypto_box_MACBYTES assert amacl == 16 # amac = b'\xa1]\xc6ML\xe2\xa9:\xc0\xdc\xab\xa5\xc4\xc7\xf4\xdb' # acrypt = (b'D\n\x17\xb6z\xd8+t)\xcc`y\x1d\x10\x0cTC\x02\xb5@\xe2\xf2\xc9-(\xec*O\xb8~\xe2\x1a\xebO') # when transmitting prepend amac to crypt acipher = pysodium.crypto_box(atob_txb, nonce, bpubkey, aprikey) assert acipher == amac + acrypt atob_rxb = pysodium.crypto_box_open_detached(acrypt, amac, nonce, apubkey, bprikey) atob_rx = atob_rxb.decode("utf-8") assert atob_rx == atob_tx assert atob_rxb == atob_txb atob_rxb = pysodium.crypto_box_open(acipher, nonce, apubkey, bprikey) atob_rx = atob_rxb.decode("utf-8") assert atob_rx == atob_tx assert atob_rxb == atob_txb btoa_tx = "Hello Alice I am Bob" btoa_txb = btoa_tx.encode("utf-8") # B encrypt to A bcrypt, bmac = pysodium.crypto_box_detached(btoa_txb, nonce, apubkey, bprikey) # bmac = b'\x90\xe07=\xd22\x8fh2\xff\xdd\x84tC\x053' # bcrypt = (b'8\xb5\xba\xe7\xcc\xae B\xefx\xe6{U\xf7\xefA\x00\xc7|\xdbu\xcfc\x01$\xa9\xa2P\xa7\x84\xa5\xae\x180') # when transmitting prepend amac to crypt bcipher = pysodium.crypto_box(btoa_txb, nonce, apubkey, bprikey) assert bcipher == bmac + bcrypt btoa_rxb = pysodium.crypto_box_open_detached(bcrypt, bmac, nonce, bpubkey, aprikey) btoa_rx = btoa_rxb.decode("utf-8") assert btoa_rx == btoa_tx assert btoa_rxb == btoa_txb btoa_rxb = pysodium.crypto_box_open(bcipher, nonce, bpubkey, aprikey) btoa_rx = btoa_rxb.decode("utf-8") assert btoa_rx == btoa_tx assert btoa_rxb == btoa_txb # compute shared key asymkey = pysodium.crypto_box_beforenm(bpubkey, aprikey) bsymkey = pysodium.crypto_box_beforenm(apubkey, bprikey) assert asymkey == bsymkey acipher = pysodium.crypto_box_afternm(atob_txb, nonce, asymkey) atob_rxb = pysodium.crypto_box_open_afternm(acipher, nonce, bsymkey) assert atob_rxb == atob_txb bcipher = pysodium.crypto_box_afternm(btoa_txb, nonce, bsymkey) btoa_rxb = pysodium.crypto_box_open_afternm(bcipher, nonce, asymkey) assert btoa_rxb == btoa_txb # crypto_box_seal public key encryption with X25519 keys # uses same X25519 type of keys as crypto_box authenticated encryption # so when converting sign key Ed25519 to X25519 can use for both types of encryption pubkey, prikey = pysodium.crypto_box_keypair() assert len(pubkey) == 32 == pysodium.crypto_box_PUBLICKEYBYTES assert len(prikey) == 32 == pysodium.crypto_box_SECRETKEYBYTES assert 48 == pysodium.crypto_box_SEALBYTES msg_txb = "Catch me if you can.".encode("utf-8") assert msg_txb == b'Catch me if you can.' cipher = pysodium.crypto_box_seal(msg_txb, pubkey) assert len(cipher) == 48 + len(msg_txb) msg_rxb = pysodium.crypto_box_seal_open(cipher, pubkey, prikey) assert msg_rxb == msg_txb # convert Ed25519 key pair to X25519 key pair # https://blog.filippo.io/using-ed25519-keys-for-encryption/ # https://libsodium.gitbook.io/doc/advanced/ed25519-curve25519 # crypto_sign_ed25519_pk_to_curve25519 # crypto_sign_ed25519_sk_to_curve25519 pubkey = pysodium.crypto_sign_pk_to_box_pk(verkey) assert len(pubkey) == pysodium.crypto_box_PUBLICKEYBYTES prikey = pysodium.crypto_sign_sk_to_box_sk(sigkey) assert len(prikey) == pysodium.crypto_box_SECRETKEYBYTES repubkey = pysodium.crypto_scalarmult_curve25519_base(prikey) assert repubkey == pubkey msg_txb = "Encoded using X25519 key converted from Ed25519 key".encode( "utf-8") cipher = pysodium.crypto_box_seal(msg_txb, pubkey) assert len(cipher) == 48 + len(msg_txb) msg_rxb = pysodium.crypto_box_seal_open(cipher, pubkey, prikey) assert msg_rxb == msg_txb """
def sk_to_public_point(self, sk): # for Ed25519, the public point is the public key and the secret exponent is the sk || pk self.public_point, self.secret_exponent = pysodium.crypto_sign_seed_keypair(seed=sk) return self.public_point, self.secret_exponent
def parse_client_keypair(keystr): m = client_seed_regex.match(keystr) if not m: return None return pysodium.crypto_sign_seed_keypair(m.group(1).decode('hex'))
def test_crypto_sign_seed_keypair(self): seed = pysodium.crypto_generichash(b'howdy', outlen=pysodium.crypto_sign_SEEDBYTES) pk, sk = pysodium.crypto_sign_seed_keypair(seed) pk2, sk2 = pysodium.crypto_sign_seed_keypair(seed) self.assertEqual(pk, pk2) self.assertEqual(sk, sk2)
#------------------------------------------------------------------------------- def human_seed(s): return to_string(VER_SEED, s) def human_account_id(s): s = hash160(s) return to_string(VER_ACCOUNT_ID, s) def human_account_public(s): return to_string(VER_ACCOUNT_PUBLIC, s) #------------------------------------------------------------------------------- if __name__ == '__main__': if len(sys.argv) > 1: seed = get_seed_generic(sys.argv[1]) else: seed = get_seed_random() public, _ = pysodium.crypto_sign_seed_keypair(seed) print 'Account ID: ', human_account_id(public) print 'Seed: ', human_seed(seed) print 'Public Key: ', human_account_public(public)