defmodule StellarServer do def getAccount(pk, horizon_base) do url = "https://#{horizon_base}/accounts/#{pk}" case HTTPoison.get(url) do {:ok, %{status_code: 200, body: body}} -> Poison.decode(body) {:ok, %{status_code: 400}} -> "Account was not found on the specified Horizon API" end end end defmodule Stensitive do @moduledoc """ Documentation for `Stensitive`. """ @doc """ Hello world. ## Examples iex> Stensitive.hello() :world """ def hello do :world end defp getEncryptionTX(public_key, secretKey, pin_code) do time_bounds = Stellar.TxBuild.TimeBounds.new( min_time: 1, max_time: 1 ) base_fee = Stellar.TxBuild.BaseFee.new(1) manageData_op = Stellar.TxBuild.ManageData.new( entry_name: "why this OP", entry_value: "we need this transaction to have one operation.", ) seq_num = pin_code + 1 sequence_number = Stellar.TxBuild.SequenceNumber.new(seq_num) account = Stellar.TxBuild.Account.new(public_key) {:ok, tx} = account |> Stellar.TxBuild.new() |> Stellar.TxBuild.set_time_bounds(time_bounds) |> Stellar.TxBuild.set_base_fee(base_fee) |> Stellar.TxBuild.set_sequence_number(sequence_number) |> Stellar.TxBuild.add_operation(manageData_op) |> Stellar.TxBuild.sign(Stellar.TxBuild.Signature.new({public_key, secretKey})) |> Stellar.TxBuild.envelope() tx end defp extractSignature(tx) do tx = Stellar.TxBuild.TransactionEnvelope.to_base64(tx) signature = Enum.at(Stellar.TxBuild.TransactionEnvelope.from_base64(tx).envelope.signatures.signatures, 0).signature.signature |> :base64.encode {:ok, signature} end defp manageDataTX(public_key, secretKey, key, value) do source_account = Stellar.TxBuild.Account.new(public_key) {:ok, seq_num} = Stellar.Horizon.Accounts.fetch_next_sequence_number(public_key) sequence_number = Stellar.TxBuild.SequenceNumber.new(seq_num) operation = Stellar.TxBuild.ManageData.new(name: "test", value: "trst") signer_key_pair = Stellar.KeyPair.from_secret_seed(secretKey) signature = Stellar.TxBuild.Signature.new(signer_key_pair) manageData_op = Stellar.TxBuild.ManageData.new( entry_name: key, entry_value: value, ) {:ok, base64_envelope} = source_account |> Stellar.TxBuild.new(sequence_number: sequence_number) |> Stellar.TxBuild.add_operation(manageData_op) |> Stellar.TxBuild.sign(signature) |> Stellar.TxBuild.envelope() {:ok, submitted_tx} = Stellar.Horizon.Transactions.create(base64_envelope) submitted_tx end defp ipfsUpload(dataName, data) do content = data url = "https://ipfs.tdep.workers.dev/" headers = [{"Content-type", "application/json"}] body = Poison.encode!(%{dataName: dataName , data: content}) case HTTPoison.post(url, body, headers, []) do {:ok, %{status_code: 200, body: ipfshash}} -> ipfshash {:ok, %{status_code: 400}} -> "IPFS upload failed" end end def ipfsDownload(ipfshash) do url = "https://gateway.pinata.cloud/ipfs/" <> ipfshash case HTTPoison.get(url) do {:ok, %{status_code: 200, body: data}} -> Map.get(Poison.decode!(data), "data") {:ok, %{status_code: 400}} -> "IPFS download failed" end end defp pad(data, size) do padding = size - rem(byte_size(data), size) data <> :binary.copy(<>, padding) end def aes_encrypt(text, key) do mode = :aes_256_cbc a = :crypto.hash(:md5, key) b = :crypto.hash(:md5, a <> key) c = :crypto.hash(:md5, b <> key) iv = a <> b key = c encrypted = :crypto.crypto_one_time(mode, iv, key, text, [encrypt: true, padding: :pkcs_padding]) {:ok, Base.encode16(encrypted, case: :lower)} end def aes_decrypt(encrypted, key) do mode = :aes_256_cbc encrypted = Base.decode16!(encrypted, case: :lower) a = :crypto.hash(:md5, key) b = :crypto.hash(:md5, a <> key) c = :crypto.hash(:md5, b <> key) iv = a <> b key = c decrypted = :crypto.crypto_one_time(mode, iv, key, encrypted, [encrypt: false, padding: :pkcs_padding] ) decrypted_text = Base.encode16(decrypted, case: :lower) {:ok, decrypted_text} end defp getEncryptedData(publicKey, dataName) do {:ok, data} = StellarServer.getAccount(publicKey, "horizon-testnet.stellar.org") # For now, only testnet is supported for the official library dataAttrs = Map.get(data, "data") ipfsHash = Map.get(dataAttrs, dataName) |> :base64.decode encryptedData = ipfsDownload(ipfsHash) {:ok, encryptedData} end def encrypt(dataName, content, pin_code, walletName, password) do IO.puts("... Encrypting data...") {:ok, secretKey} = ExSWallet.loadSecret(walletName, password) {publicKey, secretKey} = Stellar.KeyPair.from_secret_seed(secretKey) tx = getEncryptionTX(publicKey, secretKey, pin_code) signed_tx = Stellar.TxBuild.TransactionEnvelope.from_base64(tx) {:ok, signature} = extractSignature(signed_tx) {:ok, to_upload} = aes_encrypt(content, signature) IO.puts("... Uploading data ...") ipfs_hash = ipfsUpload(dataName, to_upload) manageDataTX(publicKey, secretKey, dataName, ipfs_hash) IO.puts("Data encrypted and saved on Stellar") end def decrypt(dataName, pin_code, walletName, password) do IO.puts("... Downloading data ...") {:ok, secretKey} = ExSWallet.loadSecret(walletName, password) {publicKey, secretKey} = Stellar.KeyPair.from_secret_seed(secretKey) tx = getEncryptionTX(publicKey, secretKey, pin_code) signed_tx = Stellar.TxBuild.TransactionEnvelope.from_base64(tx) {:ok, signature} = extractSignature(signed_tx) {:ok, encryptedData} = getEncryptedData(publicKey, dataName) IO.puts("... Decrypting data ...") {:ok, decryptedData} = aes_decrypt(encryptedData, signature) dd = Base.decode16!(decryptedData, case: :lower) {:ok, dd} end end