def gen_mlsag(pk, xx, index): """ Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures) These are aka MG signatutes in earlier drafts of the ring ct paper c.f. http://eprint.iacr.org/2015/1098 section 2. keyImageV just does I[i] = xx[i] * Hash(xx[i] * G) for each i Gen creates a signature which proves that for some column in the keymatrix "pk" the signer knows a secret key for each row in that column Ver verifies that the MG sig was created correctly :param pk: :param xx: :param index: :return: """ rows = len(xx) cols = len(pk) logger.debug("Generating MG sig of size %s x %s" % (rows, cols)) logger.debug("index is: %s, %s" % (index, pk[index])) c = [None] * cols alpha = scalar_gen_vector(rows) I = key_image_vector(xx) L = key_matrix(rows, cols) R = key_matrix(rows, cols) s = key_matrix(rows, cols) m = "".join(pk[0]) for i in range(1, cols): m = m + "".join(pk[i]) L[index] = [crypto.scalarmult_base(aa) for aa in alpha] # L = aG Hi = hash_key_vector(pk[index]) R[index] = [crypto.scalarmult(Hi[ii], alpha[ii]) for ii in range(0, rows)] # R = aI oldi = index i = (index + 1) % cols c[i] = crypto.cn_fast_hash(m + "".join(L[oldi]) + "".join(R[oldi])) while i != index: s[i] = scalar_gen_vector(rows) L[i] = [ crypto.add_keys2(s[i][j], c[i], pk[i][j]) for j in range(0, rows) ] Hi = hash_key_vector(pk[i]) R[i] = [ crypto.add_keys3(s[i][j], Hi[j], c[i], I[j]) for j in range(0, rows) ] oldi = i i = (i + 1) % cols c[i] = crypto.cn_fast_hash(m + "".join(L[oldi]) + "".join(R[oldi])) s[index] = [ crypto.sc_mulsub(c[index], xx[j], alpha[j]) for j in range(0, rows) ] # alpha - c * x return I, c[0], s
def test_encrypt_base(self): for i in range(10): key = crypto.cn_fast_hash(crypto.encodeint(crypto.random_scalar())) data = crypto.cn_fast_hash(crypto.encodeint( crypto.random_scalar())) * (i + 1) ciphertext = chacha.encrypt(key, data) plaintext = chacha.decrypt(key, ciphertext) self.assertEqual(plaintext, data) plaintext2 = chacha.decrypt( key, bytes(int(ciphertext[0]) ^ 0xff) + ciphertext[1:]) self.assertNotEqual(plaintext2, data)
def back_encrypt(self, authenticated=True): for i in range(5): priv_key = crypto.random_scalar() data = crypto.cn_fast_hash(crypto.encodeint( crypto.random_scalar())) * (i + 1) blob = chacha.encrypt_xmr(priv_key, data, authenticated=authenticated) plaintext = chacha.decrypt_xmr(priv_key, blob, authenticated=authenticated) self.assertEqual(data, plaintext) try: plaintext2 = chacha.decrypt_xmr( crypto.sc_add(priv_key, crypto.sc_init(1)), blob, authenticated=authenticated, ) if authenticated: self.fail("Signature error expected") else: self.assertNotEqual(data, plaintext2) except: if not authenticated: raise
def ver_mlsag(pk, I, c0, s): """ Verify MLSAG :param pk: :param I: :param c0: :param s: :return: """ rows = len(pk[0]) cols = len(pk) logger.debug("verifying MG sig of dimensions %s x %s" % (rows, cols)) c = [None] * (cols + 1) c[0] = c0 L = key_matrix(rows, cols) R = key_matrix(rows, cols) m = "".join(pk[0]) for i in range(1, cols): m = m + "".join(pk[i]) i = 0 while i < cols: L[i] = [crypto.add_keys2(s[i][j], c[i], pk[i][j]) for j in range(0, rows)] Hi = hash_key_vector(pk[i]) R[i] = [crypto.add_keys3(s[i][j], Hi[j], c[i], I[j]) for j in range(0, rows)] oldi = i i = i + 1 c[i] = crypto.cn_fast_hash(m + "".join(L[oldi]) + "".join(R[oldi])) return c0 == c[cols]
def set_seed(self, seed, path=None, slip0010=False): """ Sets master secret for BIP44 derivation :param seed: :param path: :param slip0010: :return: """ self.master_seed = seed self.is_slip0010 = slip0010 if path is None: self.path = DEFAULT_BIP44_PATH if not slip0010 else DEFAULT_SLIP0010_PATH else: self.path = path wl = bip32.Wallet.from_master_secret(seed, use_ed25519=slip0010) # Generate private keys based on the gen mechanism. Bip44 path + Monero backward compatible data = wl.get_child_for_path(self.path) self.pre_hash = binascii.unhexlify(data.private_key.get_key()) if slip0010: self.monero_master = crypto.encodeint( crypto.decodeint(self.pre_hash)) else: # Ledger way = words -> bip39 pbkdf -> master seed -> bip32 normal with # "Bitcoin seed" seed, get private key node -> cn_fast_hash -> monero master secret self.monero_master = crypto.cn_fast_hash(self.pre_hash) self.set_monero_seed(self.monero_master)
def test_cn_fast_hash(self): inp = unhexlify( b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff6405" ) res = crypto.cn_fast_hash(inp) self.assertEqual( res, unhexlify( b"86db87b83fb1246efca5f3b0db09ce3fa4d605b0d10e6507cac253dd31a3ec16" ), )
def gen_schnorr_non_linkable(x, P1, P2, index): """ Generates simple Schnorr :param x: :param P1: :param P2: :param index: :return: """ a = crypto.random_scalar() L1 = crypto.scalarmult_base(a) s2 = crypto.random_scalar() c2 = crypto.cn_fast_hash(crypto.encodepoint(L1)) L2 = crypto.point_add( crypto.scalarmult_base(s2), crypto.scalarmult(P2 if index == 0 else P1, crypto.decodeint(c2)), ) c1 = crypto.cn_fast_hash(crypto.encodepoint(L2)) s1 = crypto.sc_mulsub(x, crypto.decodeint(c1), a) return (L1, s1, s2) if index == 0 else (L2, s2, s1)
def generate_monero_keys(seed): """ Generates spend key / view key from the seed in the same manner as Monero code does. account.cpp: crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random). :param seed: :return: """ spend_sec, spend_pub = generate_keys(crypto.decodeint(seed)) hash = crypto.cn_fast_hash(crypto.encodeint(spend_sec)) view_sec, view_pub = generate_keys(crypto.decodeint(hash)) return spend_sec, spend_pub, view_sec, view_pub
def test_signature(self): for i in range(10): priv = crypto.random_scalar() data = crypto.cn_fast_hash(bytes(bytearray([i]))) c, r, pub = crypto.generate_signature(data, priv) res = crypto.check_signature(data, c, r, pub) self.assertEqual(res, 1) res2 = crypto.check_signature(data, crypto.sc_add(c, crypto.sc_init(1)), r, pub) self.assertEqual(res2, 0)
def ver_schnorr_non_linkable(P1, P2, L1, s1, s2): """ Verifies Schnorr signature generated by gen_schnorr_non_linkable :param P1: :param P2: :param L1: :param s1: :param s2: :return: """ c2 = crypto.cn_fast_hash(crypto.encodepoint(L1)) L2 = crypto.point_add(crypto.scalarmult_base(s2), crypto.scalarmult(P2, crypto.decodeint(c2))) c1 = crypto.cn_fast_hash(L2) L1p = crypto.point_add(crypto.scalarmult_base(s1), crypto.scalarmult(P1, crypto.decodeint(c1))) if L1 == L1p: return 0 else: logger.warning("Didn't verify L1: %s, L1p: %s" % (L1, L1p)) return -1
def set_seed(self, seed, path="m/44'/128'/0'/0/0"): """ Sets master secret for BIP44 derivation :param seed: :param path: :return: """ self.master_seed = seed wl = bip32.Wallet.from_master_secret(seed) # Generate private keys based on the gen mechanism. Bip44 path + Monero backward compatible data = wl.get_child_for_path(path) self.pre_hash = binascii.unhexlify(data.private_key.get_key()) self.hashed = crypto.cn_fast_hash(self.pre_hash) self.set_monero_seed(self.hashed)
def encrypt_payment_id(payment_id, public_key, secret_key): """ Encrypts payment_id hex. Used in the transaction extra. Only recipient is able to decrypt. :param payment_id: :param public_key: :param secret_key: :return: """ derivation_p = crypto.generate_key_derivation(public_key, secret_key) derivation = bytearray(33) derivation = crypto.encodepoint_into(derivation_p, derivation) derivation[32] = 0x8b hash = crypto.cn_fast_hash(derivation) pm_copy = bytearray(payment_id) for i in range(8): pm_copy[i] ^= hash[i] return pm_copy
def encrypt_xmr(priv_key, plaintext, authenticated=True): """ Monero-like authenticated encryption with Chacha20 and EC signature :param priv_key: :param plaintext: :param authenticated: :return: """ key = generate_key(crypto.encodeint(priv_key)) ciphertext = encrypt(key, plaintext) if not authenticated: return ciphertext hash = crypto.cn_fast_hash(ciphertext) c, r, pub = crypto.generate_signature(hash, priv_key) signature = crypto.encodeint(c) + crypto.encodeint(r) return ciphertext + signature
def decrypt_xmr(priv_key, ciphertext, authenticated=True): """ Monero-like authenticated decryption with Chacha20 and EC signature :param priv_key: :param ciphertext: :param authenticated: :return: """ if authenticated: ciphertext, signature = ciphertext[:-64], ciphertext[-64:] hash = crypto.cn_fast_hash(ciphertext) pub = crypto.scalarmult_base(priv_key) c = crypto.decodeint(signature[:32]) r = crypto.decodeint(signature[32:]) res = crypto.check_signature(hash, c, r, pub) if not res: raise ValueError("Signature invalid") key = generate_key(crypto.encodeint(priv_key)) return decrypt(key, ciphertext)
def create_wallet(self, line): """ Creates a new account :return: """ if self.args.account_file: if os.path.exists(self.args.account_file): logger.error("Wallet file exists, could not overwrite") return print("Generating new wallet...") seed = crypto.random_bytes(32) wl = bip32.Wallet.from_master_secret(seed) seed_bip32_words, seed_bip32_words_indices = wl.to_seed_words() seed_bip32_b58 = wl.serialize_b58() # Generate private keys based on the gen mechanism. Bip44 path + Monero backward compatible data = wl.get_child_for_path("m/44'/128'/0'/0/0") to_hash = data.chain_code + binascii.unhexlify( data.private_key.get_key()) # to_hash is initial seed in the Monero sense, recoverable from this seed hashed = crypto.cn_fast_hash(to_hash) electrum_words = " ".join(mnemonic.mn_encode(hashed)) keys = monero.generate_monero_keys(hashed) spend_sec, spend_pub, view_sec, view_pub = keys print("Seed: 0x%s" % binascii.hexlify(seed).decode("ascii")) print("Seed bip39 words: %s" % " ".join(seed_bip32_words)) print("Seed bip32 b58: %s" % seed_bip32_b58) print("Seed Monero: 0x%s" % binascii.hexlify(hashed).decode("ascii")) print("Seed Monero wrds: %s" % electrum_words) print("") print("Spend key priv: 0x%s" % binascii.hexlify(crypto.encodeint(spend_sec)).decode("ascii")) print("View key priv: 0x%s" % binascii.hexlify(crypto.encodeint(view_sec)).decode("ascii")) print("") print("Spend key pub: 0x%s" % binascii.hexlify(crypto.encodepoint(spend_pub)).decode("ascii")) print("View key pub: 0x%s" % binascii.hexlify(crypto.encodepoint(view_pub)).decode("ascii")) self.init_with_keys(spend_sec, view_sec) print("") print("Address: %s" % self.creds.address.decode("ascii")) self.account_data = collections.OrderedDict() self.account_data["seed"] = binascii.hexlify(seed).decode("ascii") self.account_data["spend_key"] = binascii.hexlify( crypto.encodeint(spend_sec)).decode("ascii") self.account_data["view_key"] = binascii.hexlify( crypto.encodeint(view_sec)).decode("ascii") self.account_data["meta"] = collections.OrderedDict([ ("addr", self.creds.address.decode("ascii")), ("bip44_seed", binascii.hexlify(seed).decode("ascii")), ("bip32_39_words", " ".join(seed_bip32_words)), ("bip32_b58", seed_bip32_b58), ("monero_seed", binascii.hexlify(hashed).decode("ascii")), ("monero_words", electrum_words), ]) if self.args.account_file: with open(self.args.account_file, "w+") as fh: json.dump(self.account_data, fh, indent=2) print("Wallet generated") self.update_prompt()
def test_cn_fast_hash(self): inp = bytes( [ 0x25, 0x9e, 0xf2, 0xab, 0xa8, 0xfe, 0xb4, 0x73, 0xcf, 0x39, 0x05, 0x8a, 0x0f, 0xe3, 0x0b, 0x9f, 0xf6, 0xd2, 0x45, 0xb4, 0x2b, 0x68, 0x26, 0x68, 0x7e, 0xbd, 0x6b, 0x63, 0x12, 0x8a, 0xff, 0x64, 0x05, ] ) res = crypto.cn_fast_hash(inp) self.assertEqual( res, bytes( [ 0x86, 0xdb, 0x87, 0xb8, 0x3f, 0xb1, 0x24, 0x6e, 0xfc, 0xa5, 0xf3, 0xb0, 0xdb, 0x09, 0xce, 0x3f, 0xa4, 0xd6, 0x05, 0xb0, 0xd1, 0x0e, 0x65, 0x07, 0xca, 0xc2, 0x53, 0xdd, 0x31, 0xa3, 0xec, 0x16, ] ), )