Skip to content
This repository has been archived by the owner on Sep 19, 2024. It is now read-only.

Commit

Permalink
docs'r'us
Browse files Browse the repository at this point in the history
  • Loading branch information
erhant committed Oct 15, 2023
1 parent dc93eaa commit 9a09c4e
Showing 1 changed file with 57 additions and 44 deletions.
101 changes: 57 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<code>huffd1</code>
</h1>
<p align="center">
<i>An NFT with Huff, using polynomials over a finite field of the largest 20-byte prime, instead of mappings.</i>
<i>An NFT with Huff, using polynomials over a finite field of order the largest prime address, instead of mappings.</i>
</p>
</p>

Expand All @@ -16,7 +16,9 @@
</a>
</p>

`huffd1` is a non-fungible token implementation in Huff, where the ownership of a token is given by the evaluation of a polynomial $P(x)$, defined over the finite field of prime order $p = 2^{160} - 47$, the largest 160-bit prime:
## Methodology

`huffd1` is a non-fungible token implementation in Huff, where instead of ownership and approval mappings, we use polynomials $P[X]$ defined over the finite field of prime order $p = 2^{160} - 47$ that is the largest 160-bit prime:

$$
p = \mathtt{0xff}\ldots\mathtt{ffd1}
Expand All @@ -28,44 +30,25 @@ $$
P \in \mathbb{F}_{\mathtt{0xff}\ldots\mathtt{ffd1}}^{n-1}[X]
$$

At contract deployment, all tokens are owned by the contract owner; and as a result the main polynomial is simply a constant polynomial equal to the owner address $P(x) = \mathtt{owner}$.

To transfer a token, we simply have to find the difference between the sender and recipient addresses, multiply that value with the basis polynomial of the respective token, and add that to the main polynomial. See `transfer` subsection below for more details.

> <picture>
> <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/Mqxx/GitHub-Markdown/main/blockquotes/badge/light-theme/note.svg">
> <img alt="Warning" src="https://raw.githubusercontent.com/Mqxx/GitHub-Markdown/main/blockquotes/badge/dark-theme/note.svg">
> </picture><br>
>
> The stack comments are written in reverse order:
>
> ```c
> opcode // [top-N, ..., top-1, top]
> pop // [top-N, ..., top-1]
> 0x01 // [top-N, ..., top-1, 0x01]
> ```
>
> unlike the usual order described in the [Style Guide](https://docs.huff.sh/style-guide/overview/).
## Usage
Let's describe each function of [`huffd1`](./src/Huffd1.huff):
Denote that ownership polynomial and approvals polynomial as $B[X]$ and $A[X]$ respectively. At contract deployment, all tokens are owned by the contract owner; and as a result the main polynomial is simply a constant polynomial equal to the owner address $P[X] = \mathtt{owner}$. There are also no approvals at first, so it is also a constant polynomial $A[X] = 0$.

### `ownerOf`
### Updating a Polynomial

To find the owner of a token $t$, simply evaluate $P(t)$ and the result will be a 160-bit number corresponding to the owner address. We use [Horner's method](https://zcash.github.io/halo2/background/polynomials.html#aside-horners-rule) to efficiently evaluate our polynomial.
In a mapping `A` we could access the value stored at `t` as `A[t]`. Now, with a polynomial $A$, we evaluate $A(t)$ to find the "stored" value. The trick is on how one would update a polynomial the same way one could update a mapping.

Initially, all tokens are owned by the contract deployer, which can be represented by the constant polynomial that is equal to the owner address.
We treat the polynomial as an interpolation over many Lagrange basis polynomials $L_0, L_1, \ldots, L_{T-1}$ over $T$ points (total supply), where $L_t[X]$ is defined as a polynomial that is equal to 1 at $t$ and 0 on all other points.

### `balanceOf`
To update $A(t)$ to any value, we can surgically remove the current evaluation, and replace it with our own using the Lagrange basis for that point $t$. For example, to replace $A(t) = n$ with $A(t) = m$ we do:

To find the balance of an address, iterate over all tokens and call `ownerOf`, counting the number of matching addresses along the way.
$$
A[X] := A[X] + L_t[X](m - n)
$$

### `transfer`
Note that since we are operating over a finite-field, multiplications will use `MULMOD` and additions will use `ADDMOD`. ALso note that $-a$ is obtained by $p-a$ where $p$ is the order of the finite field.

To transfer a token $t$ from address $a \to b$, update $P(x)$ to be $P(x) + L_t(x)(b - a)$. Here, $L_t(x)$ is the Lagrange basis of the token, defined as a polynomial that is equal to 1 at $t$ and 0 on all other points. These polynomials are computed before deploying the contract, and are stored within the contract bytecode.
### Storing the Basis Polynomials

We have a [Sage script](./src/Huffd1.sage) that can export the basis polynomials, one for each token id, as a codetable where the coefficients of each polynomial are concatenated.
The basis polynomials are computed before deploying the contract, and are stored within the contract bytecode. We have a [Sage script](./src/Huffd1.sage) that can export the basis polynomials, one for each token id, as a codetable where the coefficients of each polynomial are concatenated.

```c
// basis polynomials coefficients
Expand All @@ -74,10 +57,10 @@ We have a [Sage script](./src/Huffd1.sage) that can export the basis polynomials
}

// number of tokens
#define constant TOTAL_SUPPLY = 0xa
#define constant TOTAL_SUPPLY = 0xa // 10 tokens

// number of bytes per coefficient
#define constant COEFF_SIZE = 0x14
#define constant COEFF_SIZE = 0x14 // 20 bytes

// order of the finite field
#define constant ORDER = 0xffffffffffffffffffffffffffffffffffffffd1
Expand All @@ -92,17 +75,36 @@ Using these, we can load polynomials from the code table.
>
> The codetable grows pretty large for 20-byte coefficient size, and may easily get past the 24KB maximum contract size. To be more specific, for coefficient size `S` and total supply `N`, you will have `N` polynomials with `N` coefficients each, with `S` bytes per coefficient, resulting in total number of bytes of `N * N * S`.
This operation results in multiplying the coefficients of $L_t(x)$ with $(b - a)$ which we will do via `MULMOD`, and afterwards summation of the coefficients of $P(x)$ and the resulting polynomial from previous step, using `ADDMOD`.
### Evaluating the Polynomial
We use [Horner's method](https://zcash.github.io/halo2/background/polynomials.html#aside-horners-rule) to efficiently evaluate our polynomial. With this method, instead of:
$$
a_0 + a_1X + a_2X^2 + \ldots a_{T-1}X^{T-1}
$$
we do:
$$
a_0 + X(a_1 + X(a_2 + \ldots + X(a_{T_2} + Xa_{T-1}))
$$
Also note that $-a$ is obtained by $p-a$ where $p$ is the order of the finite field.
which also plays nicely with the reverse coefficient form that we use to store the polynomials.
### `name`
## Usage
Returns the string `"Huffd1"`.
[`huffd1`](./src/Huffd1.huff) implements the following methods:
### `symbol`
- `name`: returns the string `"Huffd1"`.
- `symbol`: returns the string `"FFD1"`.
- `ownerOf`: evaluates $B[X]$ at the given token id `t`.
- `balanceOf`: evaluates $B[X]$ over all token ids, counting the matching addresses along the way (beware of gas).
- `transfer`: updates $B[X]$ with the new address.
- `transferFrom`: updates $B[X]$ with the new address.
- `approve`: updates $A[X]$ with the new address.
- `getApproved`: evaluates $A[X]$ at the given token id `t`.
Returns the string `"FFD1"`.
It also includes ownership stuff in [`Owned.huff`](./src/util/Owned.huff) imported from [Huffmate](https://github.com/huff-language/huffmate/blob/main/src/auth/Owned.huff). Polynomial operations are implemented under [`Polynomial.huff`](./src/util/Polynomial.huff).
## Testing
Expand All @@ -119,14 +121,25 @@ forge t -vvv --mc Huffd1

I use `-vvv` to see reverts in detail.

> <picture>
> <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/Mqxx/GitHub-Markdown/main/blockquotes/badge/light-theme/note.svg">
> <img alt="Warning" src="https://raw.githubusercontent.com/Mqxx/GitHub-Markdown/main/blockquotes/badge/dark-theme/note.svg">
> </picture><br>
>
> The stack comments are written in reverse order:
>
> ```c
> opcode // [top-N, ..., top-1, top]
> pop // [top-N, ..., top-1]
> 0x01 // [top-N, ..., top-1, 0x01]
> ```
>
> unlike the usual order described in the [Style Guide](https://docs.huff.sh/style-guide/overview/).

## Remarks

- This project was done for the [Huff hackathon](https://huff.sh/hackathon)!

- This is a very inefficient contract both in contract size and gas usage, and was done mostly for the nerd-snipability factors of using polynomials instead of mappings.
- We can implement approvals with another polynomial too, but time did not permit. Also, there are many optimizations to do in many different places within the code.
- We could also use $p = 2^{160} + 7$, but I wanted all coefficients to be strictly 160-bits, which is not the case with that prime. In fact, the concept works for any prime order, but we would like to use an order that can fit almost all the addresses while being as large as an address.

- Maybe use foundry FFI to generate the basis polynomials during contract creation?

0 comments on commit 9a09c4e

Please sign in to comment.