Skip to content

Commit

Permalink
chore: ini riley puzzle eth:3 writeup
Browse files Browse the repository at this point in the history
  • Loading branch information
fiveoutofnine committed Dec 3, 2023
1 parent 31e021c commit aef5826
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 46 deletions.
Binary file added images/doge-secp256k1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 26 additions & 27 deletions puzzles/eth/1.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
author: "0xA85572Cd96f1643458f17340b6f0D6549Af482F5"
contributors: ["0xA85572Cd96f1643458f17340b6f0D6549Af482F5"]
author: '0xA85572Cd96f1643458f17340b6f0D6549Af482F5'
contributors: ['0xA85572Cd96f1643458f17340b6f0D6549Af482F5']
---

## Overview
Expand Down Expand Up @@ -94,10 +94,9 @@ function check(uint256 _shifted, uint256 _shifts) internal pure returns (bool) {
```

<Callout intent="neutral">
`check` uses the same technique used in `generate`, except it can check the
existence of $[1, 8]$ in _any_ set of 8 indices by taking in a series of
shifts to apply after each iteration, rather than just shifting by 4 bits
every time.
`check` uses the same technique used in `generate`, except it can check the existence of $[1, 8]$
in _any_ set of 8 indices by taking in a series of shifts to apply after each iteration, rather
than just shifting by 4 bits every time.
</Callout>

This is very helpful because Sudoku is quite repetitive: for each row, column, and subgrid, we perform the same check. With the way the helper function is set up, we can do that easily by defining the index shifts to look at for a row, column, or subgrid.
Expand All @@ -107,44 +106,44 @@ For example, for a subgrid, we'd want to shift by 4 bits, then 4, 20, 4, 4, 4, a
<ComponentsDisplay>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "0.5rem",
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '0.5rem',
}}
>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(8, 1fr)",
border: "1px solid #27303d",
backgroundColor: "#12161f",
borderRadius: "0.75rem",
overflow: "hidden",
color: "#c1cdd9",
display: 'grid',
gridTemplateColumns: 'repeat(8, 1fr)',
border: '1px solid #27303d',
backgroundColor: '#12161f',
borderRadius: '0.75rem',
overflow: 'hidden',
color: '#c1cdd9',
}}
>
{new Array(64).fill(null).map((_, i) => (
<div
key={i}
style={{
fontSize: "0.75rem",
fontFamily: "Menlo, monospace",
width: "2rem",
height: "2rem",
display: "flex",
alignItems: "center",
justifyContent: "center",
backgroundColor: (i & 7) > 3 && i > 48 ? "#092e52" : "transparent",
color: (i & 7) > 3 && i > 48 ? "#7697ee" : "#c1cdd9",
fontSize: '0.75rem',
fontFamily: 'Menlo, monospace',
width: '2rem',
height: '2rem',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: (i & 7) > 3 && i > 48 ? '#092e52' : 'transparent',
color: (i & 7) > 3 && i > 48 ? '#7697ee' : '#c1cdd9',
}}
>
{252 - i * 4}
</div>
))}
</div>
<div
style={{ fontSize: "0.875rem", color: "#758195" }}
style={{ fontSize: '0.875rem', color: '#758195' }}
children="Numbers represent LSb positions"
/>
</div>
Expand Down
8 changes: 4 additions & 4 deletions puzzles/eth/2.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
author: "0xA85572Cd96f1643458f17340b6f0D6549Af482F5"
contributors: ["0xA85572Cd96f1643458f17340b6f0D6549Af482F5"]
author: '0xA85572Cd96f1643458f17340b6f0D6549Af482F5'
contributors: ['0xA85572Cd96f1643458f17340b6f0D6549Af482F5']
---

## Overview
Expand Down Expand Up @@ -31,8 +31,8 @@ If you submitted before the timestamp `1678446000` (March 10, 2023 11 AM; roughl
- The next 2 bytes were picked from the submitting address based on the block number at submission.

<Callout intent="neutral">
If you submitted before the timestamp, you had a time constraint to compute,
mine, and execute the entire solution in 256 blocks.
If you submitted before the timestamp, you had a time constraint to compute, mine, and execute the
entire solution in 256 blocks.
</Callout>

If you submitted after the timestamp, there was no time constraint, and you just had to mine for a contract with 4 `0` bytes.
51 changes: 51 additions & 0 deletions puzzles/eth/3.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
author: '0xB958d9FA0417E116F446D0A06D0326805Ad73Bd5'
contributors: ['0xB958d9FA0417E116F446D0A06D0326805Ad73Bd5']
adapted_from: 'https://twitter.com/rileyholterhus/status/1637905710095933441'
---

## secp256k1 lore

Although the challenge is only a few lines of code, solving it involves traveling back ~25 years to when the parameters of the [secp256k1 curve](https://en.bitcoin.it/wiki/Secp256k1) were chosen.

<Image
src="/images/doge-secp256k1.png"
alt={`Screenshot of Walden messaging the question "What is the water bucket puzzle?" to ChatGPT, and ChatGPT explaining that it's a classic math problem that involves filling or transferring water between different-sized buckets to achieve a goal.`}
width={453}
height={467}
/>

Before we even get to the puzzle, we can take a closer look at the [specification of secp256k1](https://www.secg.org/SEC2-Ver-1.0.pdf) (the curve used by Bitcoin/Ethereum). This was released by Certicom in 2000, but unfortunately, the parameter selection process wasn't super descriptive:

> Parameters associated with a Koblitz curve admit especially efficient implementation. The name Koblitz curve is best-known when used to describe binary anomalous curves over $\mathbb{F}_{2^m}$ which have $a,b\in\{0,1\}$, [9]. Here it is generalized to refer also to curves over $\mathbb{F}_p$ which possess an efficiently computable endomorphism [7]. <span style={{ backgroundColor: "", color: "" }} children="The recommended parameters associated with a Koblitz curve were chosen by repeatedly selecting parameters admitting an efficiently computable endomorphism until a prime order curve was found." />
For discussion about this, we can look at [this Bitcoin Talk discussion](https://bitcointalk.org/index.php?topic=289795.0). Despite the limited documentation, people were able to reverse engineer reasonable explanations for the parameter chioces, including:

- $p$ being chosen as a [generalized Mersenne prime](https://en.wikipedia.org/wiki/Solinas_prime) ([helps with computing modular reductions](https://en.wikipedia.org/wiki/Solinas_prime#Modular_reduction_algorithm)).
- The coefficients ($a$ and $b$) of the curve equation being the smallest values you can choose that result in a prime order (the chosen $a$ and $b$ also allow for the [GLV method optimization](https://link.springer.com/chapter/10.1007/3-540-44647-8_11), which is even more possible explanation).

But despite all this reverse engineering, there was/is no good explanation for how the generator point $G$ was selected. Because of how the other parameters were chosen, pretty much _any_ point on the curve could have been used as $G$, so why not something "normal looking"? Well, there still isn't a great answer for this. In fact, even more questions arose when Gregory Maxwell discovered the point $G/2$ has an anomalously small $x$-coordinate, as he describes [here](https://crypto.stackexchange.com/questions/60420/what-does-the-special-form-of-the-base-point-of-secp256k1-allow/76010#76010).

This `x`-coordinate is basically the same in $G/2$ for _every_ secp\_\_\_k1 curve, which implies there was a common initial $x$-coordinate point that was slightly altered and then doubled to get $G$ for each curve. The value is ~160 bits, which means it could be a SHA1 output:

<ComponentsDisplay>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.25rem' }}>
<code style={{ color: '#758195', fontSize: '0.875rem' }}>
<span children="0x0000000000000000000000004" />
<span children="8ce563f89a0ed9414f5aa28ad0d96d6795f9c6" style={{ color: '#c1cdd9' }} />
<span children="2" />
</code>
<code style={{ color: '#758195', fontSize: '0.875rem' }}>
<span children="0x00000000000000000554123b7" />
<span children="8ce563f89a0ed9414f5aa28ad0d96d6795f9c6" style={{ color: '#c1cdd9' }} />
<span children="6" />
</code>
<code style={{ color: '#758195', fontSize: '0.875rem' }}>
<span children="0x00000000000000000000003b7" />
<span children="8ce563f89a0ed9414f5aa28ad0d96d6795f9c6" style={{ color: '#c1cdd9' }} />
<span children="3" />
</code>
</div>
</ComponentsDisplay>

This only adds to the mystery, since the "doubling" was never documented, doesn't seem necessary, and we don't know this SHA1 preimage. Regardless, the actual choice of $G$ doesn't affect the security of the curve in any way, but it does allow a very interesting "trick" in ECDSA.
26 changes: 11 additions & 15 deletions puzzles/eth/4.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
author: "0xd84365dAd6e6dB6fa2d431992acB1e050789bE69"
contributors: ["0xd84365dAd6e6dB6fa2d431992acB1e050789bE69"]
author: '0xd84365dAd6e6dB6fa2d431992acB1e050789bE69'
contributors: ['0xd84365dAd6e6dB6fa2d431992acB1e050789bE69']
---

First, to address the name of the puzzle, it is a play on words that is meant to sound similar to "water buckets". The water buckets puzzle is a classical logic puzzle around which I based this CTF puzzle. Here is what ChatGPT has to say:
Expand Down Expand Up @@ -55,8 +55,8 @@ capacity_1 | capacity_2 | volume_1 | volume_2
```

<Callout intent="neutral">
In other words, `state` encodes two water buckets of different capacities and
the current volume of water in each one.
In other words, `state` encodes two water buckets of different capacities and the current volume
of water in each one.
</Callout>

Then, we can treat `op` as a three-bit command. Going through every value of `op`, we can figure out what each one does:
Expand All @@ -73,9 +73,8 @@ Note that these first four opcodes are all implemented in the first `if` conditi
- `op == 5` and `op == 7` do nothing!

<Callout intent="neutral">
Essentially, the `work` function lets us take our two buckets and fill them
up, empty them, or pour water between them. This is exactly what we are
allowed to do in the classical logic puzzle.
Essentially, the `work` function lets us take our two buckets and fill them up, empty them, or
pour water between them. This is exactly what we are allowed to do in the classical logic puzzle.
</Callout>

The `workAll` function now wraps this `work` function to let you pass in a sequence of 85 commands to operate on an input state:
Expand Down Expand Up @@ -118,8 +117,8 @@ if (
```

<Callout>
The hardest puzzle you could have gotten would have been to start with buckets
of capacities 59 and 41.
The hardest puzzle you could have gotten would have been to start with buckets of capacities 59
and 41.
</Callout>

## Solving the puzzle
Expand Down Expand Up @@ -150,8 +149,7 @@ What do we do now? Well turns out we can kind of repeat this:
You might notice here that we essentially keep filling up one bucket, pouring it into the other until the other one is full, and then dump the other bucket, repeating until we get the right amount. If were to try this for enough combinations of bucket capacities, you would notice you always get a winning strategy out of these sorts of repeated operations.

<Callout>
If your numbers were small enough, you might have even been able to do this by
hand.
If your numbers were small enough, you might have even been able to do this by hand.
</Callout>

### Method 3: Math
Expand All @@ -165,15 +163,13 @@ If your numbers were too large to find a solution by hand or if you just like th
A bucket will only be partially full when it contains what remains of an earlier pour into the other bucket that stopped early because the other bucket reached its capacity. Thus, we would find the other bucket to be full. The other bucket might also be empty if it was emptied afterward. But, the other bucket certainly cannot be partially full.

<Callout intent="neutral">
**Key Idea 2**: You should never fill or empty the bucket that is partially
full.
**Key Idea 2**: You should never fill or empty the bucket that is partially full.
</Callout>

As we saw from method 2, the partially full bucket represents all of your work up to this point. To empty that bucket or fill it up will cause your partial work to disappear.

<Callout intent="neutral">
**Key Idea 3**: You will thus only ever fill an empty bucket or empty a full
bucket.
**Key Idea 3**: You will thus only ever fill an empty bucket or empty a full bucket.
</Callout>

This comes directly from Key Idea 2.
Expand Down

0 comments on commit aef5826

Please sign in to comment.