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>>, 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