> For the complete documentation index, see [llms.txt](https://docs.aeterna.works/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.aeterna.works/features/download.md).

# Download

Getting files after upload on Aeterna

This feature reconstructs and decrypts the files if needed. It is available through the distinct Download page or Library page's Download button for the saved files respectively.&#x20;

{% embed url="<https://files.gitbook.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyZ5q78fKbNzI8DhXbgqt%2Fuploads%2FQRj4onXiXVSMno5Ez3ZS%2Flibrary_download.mp4?alt=media&token=cc7f62b8-b1eb-4735-8858-81c34a5dd8ce>" %}

The Download page allows to download the file in 3 ways:

* Just the file\_hash (access hash), which you can get from the NFT, Library's "Get Key" button or on-chain data
* Full metadata with chain\_id, storage address etc from NFT or Library's "Get Key" button

The idea is this (EVM data storage):

<figure><img src="https://1590131134-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FU16T4AqVkNnmxdWlVSYr%2Fuploads%2FroXsQv0Soxg2IHR8KxsH%2Fimage.png?alt=media&amp;token=04086634-b301-4a42-9f9d-c27d9228f66e" alt=""><figcaption></figcaption></figure>

Decryption happens on frontend inside the browser ([Noble library](https://paulmillr.com/noble/) is used), however, to avoid `private_key` exposure, you can also save the encrypted file and decrypt it offline or in VM/sandbox. The standalone tool is here:

<https://github.com/aeterna-docs/portable>

***

### Manual reconstruction

{% hint style="info" %}
This method is only valid for Alpha 1.0 release. In next releases the system will be refactored for even cheaper storage and the method listed here might become irrelevant
{% endhint %}

As you know, Aeterna is built to serve its purpose even if it is no longer online — what it means is that the user, who has uploaded the file in the past, will still be able to get their file themselves by hand without complex tools or coding knowledge.&#x20;

Here is a simple example how to reconstruct the file with just a bit of persistence. The file in question is not encrypted and is stored permanently, meaning the Blobs ([EIP-4844](https://eips.ethereum.org/EIPS/eip-4844)) were not enabled (reconstructing files from Blobs would require a different approach since the data there is stored in sidecars and not in execution layer (EL) of Ethereum, but in Consensus layer (CL) for 4096 epochs).

The upload route used in this example is Metamask, for Relayer route, there is one extra step with an RPC call to find the transaction hash through `eth_getLogs` with inputs of `block_number`, and `storage_contract` (and optionally `event_signature` and `file_hash` for `topics` filter). Example in Python:

<details>

<summary>RPC call to find the transaction hash</summary>

```python
import requests
import json

# Inputs
RPC_URL = "https://ethereum-sepolia-rpc.publicnode.com"
BLOCK_NUMBER = 8787718
CONTRACT_ADDRESS = "0x7Bcd679D0892534bdD6EE083fdB542228f4E4702"

# RPC payload
payload = {
    "jsonrpc": "2.0",
    "method": "eth_getLogs",
    "params": [{
        'fromBlock': hex(BLOCK_NUMBER),
        'toBlock': hex(BLOCK_NUMBER),
        'address': CONTRACT_ADDRESS,
    }],
    "id": 1
}

# Make RPC call
response = requests.post(RPC_URL, json=payload, timeout=30)
result = response.json()

# Extract transaction hashes
events = result.get('result', [])
tx_hashes = [event['transactionHash'] for event in events if 'transactionHash' in event]

print(f"Found {len(tx_hashes)} transaction(s):")
for i, tx_hash in enumerate(tx_hashes, 1):
    print(f"{i}. {tx_hash}") 
```

</details>

***

#### Reconstruction steps

1. Locate the Access Key  — assuming the user has minted an NFT during upload like [this](https://eth-sepolia.blockscout.com/token/0x93c63360C27AFeEC622EA44F85722a0903958B04/instance/7?tab=metadata)

<details>

<summary>We gonna take this Access Key as an example</summary>

{% code lineNumbers="true" %}

```javascript
{
  "description": "This key grants access to a file stored without NFT ownership.",
  "download_key": {
    "file_hash": "0x46269878048d81baccd012e4c1c9b0742130c457eb58439ecce1650adbfc4670",
    "original_hash": "0x5e6af3df1c629d8685b2f1b2a7615b0492318e73e92ff84732288dba64ab2503",
    "original_size": 593,
    "storage_contract": "0x7Bcd679D0892534bdD6EE083fdB542228f4E4702",
    "nft_contract": null,
    "token_id": 0,
    "encryption_type": null,
    "aes_key_hash": null,
    "chain_id": 11155111,
    "block_numbers": [
      8780579
    ],
    "aetrna_version": "Alpha",
    "expiry": 0,
    "unpadded_length": 172,
    "uploader_address": "0x0C99bDA0C7C72d6A25d7907903FFeD826d06481D",
    "designatedCoSigners": [],
    "event_signature": "0x545f3b070d35461c64b7269a4ffa24f08b544c1676c4a6a3b46bae4254b6c66a"
  },
  "name": "File Access Key (No NFT)"
}
```

{% endcode %}

</details>

2. Find the `block number` in the Access Key which is `8780579` in our example
3. Find the `storage_contract` in the Access Key which is `0x7Bcd679D0892534bdD6EE083fdB542228f4E4702` in our example
4. Go the block explorer like Etherscan and search for the `storage_contract` which leads [to this page](https://sepolia.etherscan.io/address/0x7Bcd679D0892534bdD6EE083fdB542228f4E4702)
5. Look for the block number `8780579`  in the transaction list (Ctrl+F) and click on the transaction hash next to it, which leads [to this page](https://sepolia.etherscan.io/tx/0x5722c0d4305fcff59831c5c4b2f9f8c0406f4c5576971bd96e72698acb8e9343)
6. Click on "Show more details" nd then look into the `Input data` which will contain a hexadecimal string. In our case it is this:

<details>

<summary>Input data in Default view</summary>

{% code overflow="wrap" %}

```
0x75bf82d7000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001e046269878048d81baccd012e4c1c9b0742130c457eb58439ecce1650adbfc46705e6af3df1c629d8685b2f1b2a7615b0492318e73e92ff84732288dba64ab2503000000000000000000000000000000000000000000000000000000000000025100000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000ac0000000000000000000000000c99bda0c7c72d6a25d7907903ffed826d06481d0000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000000c061ea441b97130a6f7e3b689d93a136d6746573742e706e673a3a789ceb0cf073e7e592e2626060e0f5f47009626000315917303203a915e73ae60029c6e22077278675e7645e02392ce98ebe8e0c0c1bfbb9ff24b202f9cc013e21ae0d0d0d134a6c0d815cce028fc8620606bec320cc783c7f450a50ff754f17c7908a5b6f0f1a3202951c5af0d53f97975f8161140c65f02086978f9381b1dee0602c88ebe9eae7b2ce29a10900b55b26190000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044e6f6e65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
```

{% endcode %}

</details>

7. Now, lets decode whats in the input data to extract the actual file content before decompression

***

#### Decoding the Input Data

The easiest way is to paste the hexadecimal string into a notepad and split it by 32-bytes (64 characters) each of which serves its own purpose. This is table that shows the structure of the `Input Data`, you can do the same if you add line breaks (press Enter) to the `Input Data` string in the notepad — first, separate the `Function selection` (first 8 symbols after 0x), then split the rest by 32 bytes (64 symbols):

<table><thead><tr><th width="79.00006103515625" valign="top">#</th><th width="326.727294921875" valign="top">Description</th><th valign="top">Hex value</th></tr></thead><tbody><tr><td valign="top">1.</td><td valign="top">Function selection (4 bytes = 8 characters after 0x)</td><td valign="top">0x75bf82d7</td></tr><tr><td valign="top">2.</td><td valign="top">Offset to storage params data. 20 in hex means 32 (64 characters), so we skip this first word entirely.</td><td valign="top">0000000000000000000000000000000000000000000000000000000000000020</td></tr><tr><td valign="top">3.</td><td valign="top">Offset to <code>packedChunks</code> data in bytes. 1e0 is 480 in decimal (bytes) or 960 in hex characters.</td><td valign="top">00000000000000000000000000000000000000000000000000000000000001e0</td></tr><tr><td valign="top">...</td><td valign="top">...</td><td valign="top">...</td></tr><tr><td valign="top">18.</td><td valign="top">Length of <code>packedChunks</code></td><td valign="top">00000000000000000000000000000000000000000000000000000000000000c0</td></tr><tr><td valign="top">19-24.</td><td valign="top">Content of <code>packedChunks</code></td><td valign="top">61ea441b97130a6f7e3b689d93a136d6746573742e706e673a3a789ceb0cf073...</td></tr><tr><td valign="top">25.</td><td valign="top">Padding word</td><td valign="top">0000000000000000000000000000000000000000000000000000000000000000</td></tr><tr><td valign="top">...</td><td valign="top">...</td><td valign="top">...</td></tr></tbody></table>

After we found the offest to the `packedChunks` which is `1e0` = 480 bytes = 960 characters, we remove the next 960 characters after `1e0` and everything before that (1098 characters in summary).&#x20;

{% hint style="info" %}
In case if the dynamic data at the end of the struct is not zeroed and you are working with multiple blocks for concatenation, you will need to account for the 18th word (Length of `packedChunks`) which will tell where exactly to stop counting the data for part (or block) which is NOT last. The `unpadded_length` is used after the concatenation, the length of `packedChunks` is usefull for individual block that is not the last one.
{% endhint %}

Which leaves us with this:

<details>

<summary>packedChunks with padding</summary>

{% code overflow="wrap" %}

```
61ea441b97130a6f7e3b689d93a136d6746573742e706e673a3a789ceb0cf073e7e592e2626060e0f5f47009626000315917303203a915e73ae60029c6e22077278675e7645e02392ce98ebe8e0c0c1bfbb9ff24b202f9cc013e21ae0d0d0d134a6c0d815cce028fc8620606bec320cc783c7f450a50ff754f17c7908a5b6f0f1a3202951c5af0d53f97975f8161140c65f02086978f9381b1dee0602c88ebe9eae7b2ce29a10900b55b26190000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044e6f6e65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
```

{% endcode %}

</details>

8. Now we come back to the `Access Key` to find the `unpadded_length` line which is 172 in this example, which means we take only 172 bytes = 344 characters from the `packedChunks` with padding that we got now, which leaves us with this:

<details>

<summary>packedChunks without padding</summary>

{% code overflow="wrap" %}

```
61ea441b97130a6f7e3b689d93a136d6746573742e706e673a3a789ceb0cf073e7e592e2626060e0f5f47009626000315917303203a915e73ae60029c6e22077278675e7645e02392ce98ebe8e0c0c1bfbb9ff24b202f9cc013e21ae0d0d0d134a6c0d815cce028fc8620606bec320cc783c7f450a50ff754f17c7908a5b6f0f1a3202951c5af0d53f97975f8161140c65f02086978f9381b1dee0602c88ebe9eae7b2ce29a10900b55b2619
```

{% endcode %}

</details>

Now that we got the actual file content, time to reconstruct the actual file.&#x20;

***

#### File reconstruction

9. The structure of the file is this:

```
[16-byte Salt] + [Filename] + [Separator "::"] + [Zlib-Compressed Content]
```

Separator `::` is always the same value of `3a3a`, which leads us to this:

```
[salt] (first 16 bytes = 32 characters): 61ea441b97130a6f7e3b689d93a136d6
[separator ::] in hex: 3a3a
therefore, the [file_name] is: 746573742e706e67
[file_content]: 789ceb0cf073e7e592e2626060e0f5f47009626000315917303203a915e73ae60029c6e22077278675e7645e02392ce98ebe8e0c0c1bfbb9ff24b202f9cc013e21ae0d0d0d134a6c0d815cce028fc8620606bec320cc783c7f450a50ff754f17c7908a5b6f0f1a3202951c5af0d53f97975f8161140c65f02086978f9381b1dee0602c88ebe9eae7b2ce29a10900b55b2619
```

10. Convert the file name from hexadecimal to ASCII using [any online tool](https://www.rapidtables.com/convert/number/hex-to-ascii.html).&#x20;

`746573742e706e67` yields `test.png`  which is the file name we are going to use.

11. Now, the last step is decompression. Aeterna uses `zlib` for compression / decompression. `zlib` is heavily standardized and used everywhere (PNG, Zip, FLAC formats, VPNs, Linux kernel, etc). The standards are RFC [1950](https://www.rfc-editor.org/info/rfc1950) / [1951](https://www.rfc-editor.org/info/rfc1951) since 1996. So even decades later, the user will be able to decompress the file by few lines of code.

Example with Python:

```python
import zlib

file_content = "789ceb0cf073e7e592e2626060e0f5f47009626000315917303203a915e73ae60029c6e22077278675e7645e02392ce98ebe8e0c0c1bfbb9ff24b202f9cc013e21ae0d0d0d134a6c0d815cce028fc8620606bec320cc783c7f450a50ff754f17c7908a5b6f0f1a3202951c5af0d53f97975f8161140c65f02086978f9381b1dee0602c88ebe9eae7b2ce29a10900b55b2619"

decompressed_data = zlib.decompress(bytes.fromhex(file_content))

file_name = "test.png"

with open(file_name, "wb") as f:
    f.write(decompressed_data)

print(f"File {file_name} has been decompressed and saved.")
```

Running that will yeild the actual file from the blockchain. For file that were stored across multiple blocks, you will need to concatenate the file content and decompress in a same way. The structure of the files in `second + N` blocks will have no `16-byte salt`, `file_name` and separator `::` in it, only the part of content.
