pull down to refresh

During the Bitcoin++ hackathon, I built BitcoinLink as a simple showcase of how you can use Nostr Wallet Connect (NWC) to create non-custodial, one-time-use reward links redeemable via lightning. The service allows you to send bitcoin to anyone with a simple link redeemable with Lightning via NWC. The sender generates an NWC with either Alby or Mutiny, chooses a total budget and the number of links to split that budget across. BitcoinLink then generates a pseudorandom secret, encrypts the NWC with the secret, stores the secret in one or many links, and saves the encrypted NWC in our database. The receiver clicks one of the links, the app detects and decrypts the NWC, and sends the bitcoin directly to the receiver's wallet from the sender's wallet over lightning.
I was particularly proud of this scheme because it splits the encryption key and the encrypted NWC into two separate places. The server never saves the secret, and the sender can create multiple links from one NWC. However, despite this theoretically sound approach, I made a grave implementation mistake that allowed a hacker to access both the secret and the encrypted NWC, leading to the theft of one of my user's funds.

In this post, I'll walk you through the hack, how I fixed it, and what I learned from the experience.


The Hackathon

I built the first implementation of BitcoinLink at the Bitcoin++ hackathon. During development, I made a few bad assumptions about how I could build my MVP, which required some architectural changes midway through the build. This is a common occurrence in hackathons, so I made the changes and continued on. I managed to get the MVP working and submitted it to the hackathon. I was thrilled with the result and excited to see people's reactions. After the hackathon, a few people approached me, interested in using BitcoinLink for various purposes, which was encouraging. Following the hackathon, I made basic improvements to the UI and flow, added the ability to generate links with Mutiny Wallet, and created an API endpoint for programmatically pulling generated links. Unfortunately, during this time, I neglected to review my code thoroughly and audit the core flow of the app. Had I done so, I likely would have found the vulnerability before it was exploited. However, I was distracted by shiny new features and didn't take the time to clean up what had already been built.


The Hack

Last week, I woke up to a message from one of my users saying their Alby wallet had been drained of over 200,000 sats. I was shocked. I had built this service to be non-custodial and secure—how could this have happened? I quickly checked my logs and saw a trail of requests that appeared to be someone generating and claiming links, playing with the endpoints. My user had generated many links with a large budget on their NWC. The hacker got one of these links, accessed the secret, and decrypted it. But how? I reviewed my code and quickly saw that one of my most critical endpoints was left unprotected, allowing the attacker to pull down the encrypted NWC if they had the ID. I was devastated. I had failed my users. The hacker, with a valid link someone else generated, was able to grab the secret from the link, pull the encrypted NWC from my unprotected endpoint, decrypt it, and drain it of its full budget. It was such a simple oversight—I couldn't believe I had missed this critical vulnerability. Because the code was open source, the hacker was able to play around with the service, discover the vulnerability, and exploit it all within a single night.


The Fix

Once I understood how the attack happened, I immediately began working on fixes. This started simple but quickly ballooned into a full re-architecture of the service and security model. I decided to move the decryption of the NWC and the payment into the execution context of my claim endpoint so that the encrypted NWC would never be exposed to the client. Payments could only be executed server-side for the exact amount encoded in the link. I also now duplicate and re-encrypt the NWC across my database with different unique secrets for each link/NWC and unique NWC IDs for each encrypted NWC. This effectively decorrelates all links and NWCs, ensuring that even if a secret gets leaked, an attacker will never have access to the encrypted NWC and vice versa.


What I Learned

This experience taught me a lot. It was the first time a project of mine had been hacked, the first time I lost (someone else's) money due to my mistake, and the first time I had to play cat-and-mouse with a hacker trying to pwn my users in real time. I'm very disappointed in myself for letting this happen, but I know it's important to share exactly what happened and take responsibility for it. I strive to be transparent with my work—everything I've ever worked on as a developer has been open source and done in public. It would be hypocritical of me not to share my failures as well. Overall, this was an important learning experience. I gained a better understanding of security, the importance of code reviews, and the necessity of auditing your code to ensure it's secure. I have a newfound respect for the level of responsibility I have when building and shipping projects to end users, especially when they involve payments. I hope others will heed my warning and always proceed with caution and an adversarial mindset when building and deploying software.


Conclusion

With all of that out of the way, let me put my money where my mouth is.

Here is a bitcoinlink that correlates to a 100,000 sats NWC that I generated with Alby (this one is a freebie, someone will be able to claim 1000 sats): https://www.bitcoinlink.app/claim/clxj1w9sn0001zjaemkmmv0uk?secret=6fb00c23ed97261f18d7fbf5ce71e6f5d524531bdf7be9cbb5cd65885f923e9f&linkIndex=ceab5be1-1422-405d-acc3-e21a4c12c41c Here is the bitcoinlink source code
I invite anyone with the technical chops to take the information in this link and the source code and try to hack the updated version of BitcoinLink and drain my NWC. I'm fairly confident that my updated version of BitcoinLink will make this immensely more difficult, but I would love to be proven wrong. You can earn a decent bounty for doing so. If you do, please let me know how you did it so I can fix it and make it better for everyone else.

Onwards!
200k sats is not too large a price to pay for a lesson. Onwards indeed.
reply
True, I would have paid 200k in a second to get alerted of this bug, the real price for me though is the shame of letting down my users, and in general shaking the trust of everyone who follows me and uses any of my projects.
reply
Yeah the bounty for that kind of thing is worth more than 200k sats, I'm surprised they didn't just report it.
reply
No guarantee you'll get a bounty from a small project. You're more likely to get paid if you exploit it.
reply
I hear you. Rebuilding trust is key, which you have taken a step towards via this post. Gotta crack some eggs and all.
reply
218 sats \ 0 replies \ @Car 17 Jun
👏 Love to see this, leading by example and owning it.
reply
I should also mention:
There is a lot of details I have to leave out for privacy/security reasons. But I'm 99% positive I know who hacked bitcoinlink, and it is a prominent developer in the space I'm sure everyone would recognize.
I'm not absolutely certain though so I wont name them, but they could still definitely shoot me a dm ;)
reply
151 sats \ 2 replies \ @jasonb 17 Jun
Thanks for sharing this. It’s humble and educational. I’m glad to hear the loss wasn’t more significant.
If you have a primary suspect, wouldn’t it make sense to approach them? At best, that’s super unethical of the hacker to keep the funds. If someone left something physical worth 200,000 sats sitting on the table at a coffee shop while they went to the bathroom and I saw someone grab that item because it wasn’t properly secured, I’d still feel comfortable or even compelled to call them out.
I’m not saying you and the victim shouldn’t take responsibility for some mistakes, but I don’t think you should take MORE credit for the theft than the actual thief.
reply
Thanks Jason!
What was important to me was discovering the exploit and being able to fix it (which I was able to do without communicating with the hacker)
I still might try to reach out to who I think the hacker (esp if they keep trying to attack) but I'm gonna give them to chance to come to me first.
In general though I take full responsibility for the attack, the hacker was just exploiting my mistake, so it falls on me.
reply
120 sats \ 0 replies \ @jasonb 17 Jun
Sure, I mean we’re all bitcoiners here, so it’s a given in this crowd that personal responsibility is important. I’m just saying, we should call out people who actively are trying to take things from others without permission.
If I’m in a restaurant, and somebody comes up and steals the hat off my head (the victim), I probably should have been holding it in place. The restaurant (you) probably should have had better security. But the one who is acting like a jerk is still the thief.
I hope he comes forward to you, but if he doesn’t, I just think he should at least be called out for acting like a jerk. Otherwise, we’re just on the slippery slope to might makes right.
reply
Stealing is wrong! Having being scammed myself we need a solution to bring people to justice for stealing!!
What’s the point of having this new system if you can’t be protected from exploitation and once it does happen you have no way to bring that person to justice or recover your funds. I know government gets a lot of flak on this site but the whole idea behind it is to stop plunder and protect rights. Don’t let governments of today shroud your judgement of why they are even created in the first place! To bring shitbag humans who rob and steal to justice!
reply
This is why it's important to carefully vet what products/services/wallets you use in this space.
I could try to start a witch hunt for the hacker but I dont think it's the right approach.
I'm the one who let down my users, the hack was my fault, the attacker was just exploiting my mistake, so I take full responsibility.
reply
But stealing is still wrong. If that hacker had any morals he would of responsibly found the vulnerability and disclosed it to you in private to stop others from looting
reply
Congrats on being open about it and on finding a quick solution. Wish this kind of transparency would be more common in the tech industry.
reply
in general about businesses using LN:
One one hand users are intense about wanting to see the production on a wallet onchain
on the other hand keeping funds as fractional reserves and refilling them manually every once in a while from a disconnected cold wallet is so much safer. And peace of mind. 🤷🏻‍♀️
reply
This is one reason budgets on NWC should absolutely be put in place, especially on experimental services, which arguably they all are.
reply
Yeah there was a budget on the nwc, it's what kept the wallet from getting completely drained, allows the user to cap their risk, but I should have only allowed smaller NWC budgets at first.
reply
Ah, I assumed there was no budget based on the 200k amount and how you said it was drained. Glad the budget worked.
reply
Ahh yeah I could have been a little more clear with the wording, the sender sets the budget themselves by how many links they generate and the sats per link.
reply
If you leave an envelope full of cash in the middle of the street, and someone takes it, it was not stolen. Change my mind.
The internet is the public square, if your app is publicly accessible, and someone is able to make the app pay them, then they earned every sat.
You were not a victim of theft. Only a victim of loss. No different than losing your cash in the street.
reply
262 sats \ 5 replies \ @jeff 17 Jun
Your metaphor breaks down a bit, when you realize the hacker knew the rightful owner.
So, in your metaphor, its more like leaving an envelope in the street, with all your contact information on it, as well as the owner being physically nearby and ready to answer thr phone.
If you find a wallet full of cash, and the ID in it, and you take the money but return the wallet, you are taking somebody else's property.
reply
Did they know the owner? was the owner the maintainer of the app or the user of the app who connected their wallet to it?
reply
100 sats \ 1 reply \ @jeff 18 Jun
They knew the host of the service via the domain they would have been hitting, (presumably?) they knew the maintainer's handle as the code was open source, and the OP said that they knew the victim, as the hacker had some kind of identifier used in the attack. They had enough to triangulate the owner and/via the maintainer.
reply
That is brilliant use of the / character. Kudos.
reply
If someone uses weak entropy and their wallet gets drained, was that theft? Whats the difference between using weak entropy and trusting insecure software?
reply
155 sats \ 0 replies \ @jeff 18 Jun
Weak entropy is closer to the anonymous envelop. There is no way to contact the owner of a wallet created with weak entropy.
But, put it this way: if your mother created a wallet with weak entropy, and you serendipitously found the weak entropy wallet and then took the funds, but you honestly didn't know they were hers until months later when she complained about an anonymous "hacker".
Would you give her, her funds back? Would you tell her you 'just found an anonymous wallet' and so you wouldn't give it back? Would you just stay quiet, lie by omission?
Does this change, if its a stranger?
You asked to have somebody change your mind. I have tried. I have my own answers to the above. I wish you the best, in figuring out your own answers.
reply
I'm not interested in changing your mind, but I am genuinely curious.
If I break into your home when you aren't home, is anything I "take" not stolen because your lock was weak enough to break? Your home is connected to "public" like his server is connected to the public internet, isn't it?
Do you believe in physical property rights, but not digital ones? I suspect you either view digital property rights differently than physical ones, or don't believe in property rights of any kind. If the former, how do you explain the difference?
reply
Thanks for being curious! Sometimes it takes a good question to make me solidify my thinking.
If I break into your home when you aren't home, is anything I "take" not stolen because your lock was weak enough to break?
I believe that to own property is to have the ability to control the destiny of that property. The surest way to own something is to destroy it -- to realize the destiny of the property by eliminating its value as property. If someone else was able to control "my" property, then it is no longer my property, exclusively.
However, we have a legal system that would assert that I am still the "owner" of the property. That legal system can attempt to "clawback" the property and return it to my control. So, in a way, even after the property has left my direct control, I still have some control over the destiny of that property -- insofar as the property could potentially be returned to me by the legal system that enforces my "property rights".
Government is instituted for the common good; for the protection, safety, prosperity, and happiness of the people; and not for profit, honor, or private interest of any one man, family, or class of men; ~ John Adams
Property rights are great! Enforcing property rights encourages investment, discourages the destruction of property and is key to a prosperous society. But property rights can also be abused to weaponize "legalized violence" to the advantage of a particular class. Communism isn't the solution, but the communists are really good at pointing out the downsides of private property rights in practice. They tend to increase class inequality, for example. Don't get me started on "intellectual property".
Do you believe in physical property rights, but not digital ones?
I believe property rights do not exist in nature. They are constructs of states/governments/societies/communities and they exist insofar as they can be enforced.
In nature, one's ownership of property would be mostly a function of their ability to conquer and defend exclusive control over property (Ghengis Khan warlord-style).
Since Bitcoin cannot be so easily "clawed back" by a legal system using violence, I argue that BTC is somewhat immune to any external property rights. It has it's own system of enforcing ownership that relies on entropy and fundamental truths about nature/physicality.
Having "ownership" of some sats is to have the knowledge of a private key (such that you have the ability to control those sats). A private key is just a large number that is difficult to guess. Is it possible to own a number?
In the same way, do you anything (physical) fully? Couldn't the state use enough force to take or destroy everything you own? Could some random person take or destroy your property? I don't see "ownership" as a binary outcome as the legal system does. Ownership, naturally, is more like a vector.
The more power the individual wields, the more likely their ownership of a piece of property is to be exclusive. Bitcoin gives anyone access to immense power. It does it by weaponizing entropy instead of violence. Namely, the entropy required to create a secure private key and the entropy created by consuming tremendous energy.
reply
You're wrong.
This is an oversimplification and frankly the type of logic that would excuse all sorts of heinous crimes including pedophilia.
Read some Dostoevsky. Go with christ @nullcount.
reply
Let me see if I can paraphrase your argument:
I'm wrong because you said so! And the logic of my metaphor is a slippery slope.
reply
There's no slippery slope it's just an entirely wrong way of looking at ethics not only in this case but in general. Usually when people have these severe and obvious shortcomings it's because they're lying to themselves in much the same fashion that Dostoevsky masterfully illustrates in his writings.
The closest physical analog to this hack would be that someone picks the lock on your house and ransacks it. I'm sure you'd agree that it is not morally justified just because your lock sucks or because the thief had skill.
@bitcoinplebdev 100% bears the responsibility of protecting himself from predators which he failed to do and took accountability for it.
The hacker is still a piece of shit for this. There was a responsible way to demonstrate the hack, report it and receive fair (possibly even the same!) compensation as what he took. It's malice pure and simple. There's a very small pool of people that could have done it and I suspect this isn't over.
You surely believe in high values! It's difficult to establish trust, and it's way more difficult to re-establish it. But your hard work and honesty will make it easier for you. Wish you all the best!
reply
Huge learning experience. Thank you for sharing!
I wish the rest of the world had your humility.
reply
Thanks for taking ownership and admitting your failure(s). We all have them, and it is important we look back at them, build better and build our code with the thought of minimizing attack vectors.
One thing I'd recommend is maybe doing a couple bug bounties of 10K-50K sats before releasing the product. This may have been found if it went through a bounty. Cheers and best of luck on your future projects!
reply
This is one reason why I love Bitcoin. Because there are decent people working on it who will own up to their mistakes and put things right which makes Bitcoin stronger for everyone. Massive respect to you @bitcoinplebdev 🙏
reply
reply
thanks for sharing
reply
It's still good that it were NWC permissions with budget limits. Imagine they'd hack lndhub credentials
reply
I feel sad for you didn't commit any sin except a little negligence.
One way of building trust is to show that we are caring, fair, open and honest human beings. And you're on the right path. You'll surely gain back the trust of your users.
reply
I can see this project orange-pill lots of users
reply
Don't try to reinvent the wheel, things are open source for a reason. You can always +/- the source code plus an audit should have been done. You're human, just keep building.
stackers have outlawed this. turn on wild west mode in your /settings to see outlawed content.
stackers have outlawed this. turn on wild west mode in your /settings to see outlawed content.
stackers have outlawed this. turn on wild west mode in your /settings to see outlawed content.