Bypassing Railgun Proof of Innocence
The Railgun protocol wants to let users move crypto privately so nobody on the blockchain can see how much ETH (or any token) was sent or who got it. It promises to do that using on-chain tech and zero-knowledge cryptography. After launch, they added a Proof-Of-Innocence technology to verify if a given note came from a list of known-bad addresses or tainted transactions. The official SDK does not allow users to move funds if they do not have a POI generated. The problem is the POI check runs off-chain and depends on the client SDK, which lives on the user's machine. That means someone who controls or modifies their client can bypass the check.
In this post, let's take a closer look at how the Railway wallet handles the Proof-Of-Innocence system. For my tests, I grabbed the non-installer build of Railway v5.22.4-win-x64.zip, which at the time of writing was the latest release. After launching the wallet and importing my seed, the balance appeared correctly, but the app immediately flagged that a POI had to be generated before those funds could be moved.

Now for the fun part. Railway is an Electron app, so most of the code and logic lives inside the packaged application file (the app.asar). That means the wallet's behavior, including POI checks, are implemented on the client side.
After analyzing the code of the SDK, I saw that the code basicatly enforces the POI if the file /models/network-config.ts of the shared-models repository has the poi element in the JSON configuration:
export const NETWORK_CONFIG: Record<NetworkName, Network> = {
[NetworkName.Ethereum]: {
chain: {
type: ChainType.EVM,
id: 1,
},
... omited for brevity...
poi: {
launchBlock: 18514200, // Nov 06, 2023 - ~12:00pm ET
launchTimestamp: 1383760800, // Unix timestamp in seconds — Nov 06, 2023, 12:00pm ET
},
... omited for brevity...
},So to bypass the POI we just need to change that. As the Railway wallet is build in Electron, the JS has been minified. But a quick search & replace is enough to change the logic of the SDK and bypass the POI.
First we need to unpack the app.asar file:
>asar extract app.asar unpakedThen, you just need to modify the all the instances of the string ,poi: found in the .JS files by ,nopoi:, as in the figure below:

After that, I ran into a weird error while trying to generate a ZK proofs. I couldn't find a reliable way to reproduce it, but it pops out under some unknown conditions.

If you are facing the same issue, you can "fix" it by doing the following replacements:
Replace throw new Error(`Cannot cache a transaction with a 'from' address.`); by delete e.transaction.from; and throw new Error("Cannot cache a transaction with a 'from' address."); by delete e.transaction.from;.
Once the edits are done, we need to rebuild the app.asar file with the changes:
>asar pack unpaked app.asar
And voila, there we got our wallet, totally functional without POI. As Proof-Of-Innocence is enforced in the client, a modified client can simply skip it.
Note that because the POI infrastructure itself runs off-chain, these transactions will still settle on Ethereum, but they won’t appear in the Railgun POI aggregator. They may get flagged as missing POI, which may reduce the anonymity. To prevent this, I recommend to unshield without POI into a fresh new address, and then shield back using POI.
Stay safe, stay private.
Manuel