Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add examples and readme updates from hc #7

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target
2 changes: 1 addition & 1 deletion Nargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ type = "lib"
authors = [""]
compiler_version = ">=0.34.0"

[dependencies]
[dependencies]
170 changes: 157 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,48 @@
# noir_base64

A library to encode ASCII into Base64 and decode Base64 into ASCII
A library to encode ASCII into Base64 and decode Base64 into ASCII.

# Usage
## Dependencies

### `fn base64_encode`
Takees an input byte array of ASCII characters and produces an output byte array of base64-encoded characters. The 6-bit base64 characters are packed into a concatenated byte array (e.g. 4 bytes of ASCII produce 3 bytes of encoded Base64)
- Noir ≥v0.34.0
- Compatible version of a proving backend, eg Barretenberg
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove references to the backend here. If we push people towards the Nargo docs then that can teach them about the need for a proving backend.

We're going to get a lot of duplicated documentation which will fall out of sync otherwise.


### `fn base64_decode`
Takes an input byte array of packed base64 characters and produces an output byte array of ASCII characters (e.g. 3 input bytes of base64 produces 4 output bytes of ASCII)
Refer to [Noir's docs](https://noir-lang.org/docs/getting_started/installation/) and [Barretenberg's docs](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/cpp/src/barretenberg/bb/readme.md#installation) for installation steps.

### `fn base64_encode_elements`
Takes an input byte array of ASCII characters and produces an output byte array of base64-encoded characters. Data is not packed i.e. each output array element maps to a 6-bit base64 character
## Installation

### `fn base64_decode_elements`
Takes an input byte array of base64 characters and produces an output byte array of ASCII characters. Input data is not packed i.e. each input element maps to a 6-bit base64 character
In your _Nargo.toml_ file, add the version of this library you would like to install under dependency:

```
[dependencies]
noir_base64 = { tag = "v0.2.0", git = "https://github.com/noir-lang/noir_base64" }
```
## Quickstart

The library offers 4 functions; `fn base64_encode`, `fn base64_decode`, `fn base64_encode_elements` & `fn base64_decode_elements`. Find descriptions per method below.

In this example we take input `"Noir"` represented in ASCII, and encode this into Base64. The result is either "packed" together or given as separate elements. (Refer to the method descriptions below.)

Define the input in ASCII (`"N"` = 78, `"o"`= 111, `"i"`= 105, `"r"`= 114):
```rust
let input: [u8; 4] = [78, 111, 105, 114];
```

Encode either into a concatenated array or each Base64 element in a separate byte; Base64 values only take up 6 bits of space, see full explanation of the conversion + mapping table below.

### Example usage
(see tests in `lib.nr` for more examples)
```rust
// Packed
let result_packed: [u8; 3] = noir_base64::base64_encode(input);
assert(result_packed == [54, 136, 171]);

// In separate elements
let result_elements: [u8; 4] = noir_base64::base64_encode_elements(input);
assert(result_elements == [13, 40, 34, 43]);
```

A larger example:

```rust
use dep::noir_base64;
fn encode() {
// Raw bh: GxMlgwLiypnVrE2C0Sf4yzhcWTkAhSZ5+WERhKhXtlU=
Expand Down Expand Up @@ -49,7 +72,128 @@ fn encode() {
}
```

# Costs
See more examples in `lib.nr` and the `examples` folder.

## Conversion explainer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!


[Base64](https://datatracker.ietf.org/doc/html/rfc4648#section-4) is a 6-bit encoding system (`0` to `63`).

[ASCII](https://www.ascii-code.com/) is a 7-bit character code ( `0` to `127`).

Note that the character set of ASCII is larger than that of Base64. When encoding ASCII into Base64, the special characters that exist in ASCII but not in Base64 are mapped to `0`.

For encoding and decoding the library uses lookup tables. The following table shows the mapping from ASCII <-> Base64. (See for the expanded version below.)

| Character | Decimal in ASCII | Decimal in Base64 |
|:----------------|:-----------------:|:------------------:|
| `+` | 43 | 62 |
| `/` | 47 | 63 |
| `0-9` | 48,..,57 | 52,..,61 |
| `A-Z` | 65,..,90 | 0,..,25 |
| `a-z` | 97,..,122 | 26,..,51 |

### Example

For example for input "Noir" in ASCII, we have `"N"` = 78, `"o"`= 111, `"i"`= 105, `"r"`= 114:
```rust
let input_ascii: [u8; 4] = [78, 111, 105, 114];
```

The output in Base64 will be:
- `N` -> 13
- `o` -> 40
- `i` -> 34
- `r` -> 43

Base64 values are 6 bits and this library offers 2 way to output the result; `base64_encode` or `base64_encode_elements`.

For `base64_encode_elements` each value is stored in a different byte, which in this case results in: `[13, 40, 34, 43]`.
```rust
let result_elements: [u8; 4] = noir_base64::base64_encode_elements(input);
assert(result_elements == [13, 40, 34, 43]);
```

For `base64_encode` concatenate the values and then split them up into bytes. As follows:
1. Rewrite all values to binary:
```
13 | 40 | 34 | 43 <- Decimal representation
001101 | 101000 | 100010 | 101011 <- Binary representation
```
2. Glue together
```
001101101000100010101011
```
3. Split up in chunks of 8 bits
```
00110110 | 10001000 | 10101011
```
4. Convert to decimal: `[54, 136, 171]`

```rust
let result_packed: [u8; 3] = noir_base64::base64_encode(input);
assert(result_packed == [54, 136, 171]);
```

## Methods

### `fn base64_encode`
Takees an input byte array of ASCII characters and produces an output byte array of base64-encoded characters. The 6-bit base64 characters are packed into a concatenated byte array (e.g. 4 bytes of ASCII produce 3 bytes of encoded Base64)

### `fn base64_decode`
Takes an input byte array of packed base64 characters and produces an output byte array of ASCII characters (e.g. 3 input bytes of base64 produces 4 output bytes of ASCII)

### `fn base64_encode_elements`
Takes an input byte array of ASCII characters and produces an output byte array of base64-encoded characters. Data is not packed i.e. each output array element maps to a 6-bit base64 character

### `fn base64_decode_elements`
Takes an input byte array of base64 characters and produces an output byte array of ASCII characters. Input data is not packed i.e. each input element maps to a 6-bit base64 character

## Costs

`base64_encode_elements` will encode an array of 44 ASCII bytes in ~470 gates, plus a ~256 gate cost to initialize an encoding lookup table (the initialization cost is incurred once regardless of the number of decodings)

## Base64 Encoding Table (Extended)

| Character | Decimal in ASCII | Decimal in Base64 |
|:----------|:-----------------|:------------------|
| `+` | 43 | 62 |
| `/` | 47 | 63 |
| `0` | 48 | 52 |
| `1` | 49 | 53 |
| `2` | 50 | 54 |
| `3` | 51 | 55 |
| `4` | 52 | 56 |
| `5` | 53 | 57 |
| `6` | 54 | 58 |
| `7` | 55 | 59 |
| `8` | 56 | 60 |
| `9` | 57 | 61 |
| `A` | 65 | 0 |
| `B` | 66 | 1 |
| `C` | 67 | 2 |
| `D` | 68 | 3 |
| `E` | 69 | 4 |
| `F` | 70 | 5 |
| `G` | 71 | 6 |
| `H` | 72 | 7 |
| `I` | 73 | 8 |
| `J` | 74 | 9 |
| `K` | 75 | 10 |
| `L` | 76 | 11 |
| `M` | 77 | 12 |
| `N` | 78 | 13 |
| `O` | 79 | 14 |
| `P` | 80 | 15 |
| `Q` | 81 | 16 |
| `R` | 82 | 17 |
| `S` | 83 | 18 |
| `T` | 84 | 19 |
| `U` | 85 | 20 |
| `V` | 86 | 21 |
| `W` | 87 | 22 |
| `X` | 88 | 23 |
| `Y` | 89 | 24 |
| `Z` | 90 | 25 |
| `a` | 97 | 26 |
| `b` | 98 | 27 |
| `c` | 99 | 28 |
2 changes: 2 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
These examples will be moved to another repo so that they can be tested/updated.
`nargo` currently ascends the directory structure to the highest found Nargo.toml file.
8 changes: 8 additions & 0 deletions examples/example1/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "example1"
type = "bin"
authors = [""]
compiler_version = ">=0.34.0"

[dependencies]
noir_base64 = { path = "../.." }
46 changes: 46 additions & 0 deletions examples/example1/src/main.nr
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests can be moved into the main library package (and we'll then get them tested in CI)

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use dep::noir_base64;

fn main() {}

#[test]
fn test_encode_elements_noir() {
// Convert "Noir" in ASCII to Base64

// "Noir" in ASCII equals [78, 111, 105, 114]
let input: [u8; 4] = [78, 111, 105, 114];

// Mapping to Base64
// N -> 13
// o -> 40
// i -> 34
// r -> 43
let result: [u8; 4] = noir_base64::base64_encode_elements(input);
assert(result == [13, 40, 34, 43]);
}

#[test]
fn test_encode_packed_noir() {
// Convert "Noir" in ASCII to Base64 (packed)

// "Noir" in ASCII equals [78, 111, 105, 114]
let input: [u8; 4] = [78, 111, 105, 114];

// Mapping to Base64
// N -> 13
// o -> 40
// i -> 34
// r -> 43
let result: [u8; 3] = noir_base64::base64_encode(input);

// Base64 values are 6 bits.
// Instead of putting each of them in a separate byte, glue them together and then chop up into bytes:

// 13 | 40 | 34 | 43
// 001101 | 101000 | 100010 | 101011
// 001101101000100010101011 <- glue together
// 00110110 | 10001000 | 10101011 <- chop up in bytes
// 54 | 136 | 171 <- decimal representation

assert(result == [54, 136, 171]);
}

8 changes: 8 additions & 0 deletions examples/example2/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "example2"
type = "bin"
authors = [""]
compiler_version = ">=0.34.0"

[dependencies]
noir_base64 = { path = "../.." }
1 change: 1 addition & 0 deletions examples/example2/Prover.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
input = ["71", "120", "77", "108", "103", "119", "76", "105", "121", "112", "110", "86", "114", "69", "50", "67", "48", "83", "102", "52", "121", "122", "104", "99", "87", "84", "107", "65", "104", "83", "90", "53", "43", "87", "69", "82", "104", "75", "104", "88", "116", "108", "85", "61"]
24 changes: 24 additions & 0 deletions examples/example2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Example 2

For this example the correct values have already been added to `Prover.toml`. Execute the circuit:
```
nargo execute base64
```

Prove it, for example with default backend Barretenberg:
```
bb prove -b ./target/example2.json -w ./target/base64.gz -o ./target/proof
```

To verify, we need to export the verification key:

```bash
bb write_vk -b ./target/example2.json -o ./target/vk
```

And verify:

```bash
bb verify -k ./target/vk -p ./target/proof
```
If verification passed, you see nothing. Otherwise there is an error.
Comment on lines +8 to +24
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't include documentation on how to generate proofs on Noir libraries. We can centralize that into one place and then any library documentation should only teach how to interact with the library through Noir code.

12 changes: 12 additions & 0 deletions examples/example2/src/main.nr
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not clear on what this example is showing over example1

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use dep::noir_base64;

fn main(input: [u8; 44]) {
let result: [u8; 32] = noir_base64::base64_encode(input);
let expected = [
27, 19, 37, 131, 2, 226, 202, 153, 213, 172,
77, 130, 209, 39, 248, 203, 56, 92, 89, 57,
0, 133, 38, 121, 249, 97, 17, 132, 168, 87,
182, 85
];
assert(result == expected);
}
Loading