Wallet QR is a website I created as a part of my final year of university. The core concept behind it is to be a place for users to share their cryptocurrency wallet addresses with others by entering them into publicly available profiles. It's named after QR codes because each added cryptocurrency is displayed with a QR code for ease of use.
Wallet QR exists in two halves; there's the statically-generated Angular app and a server-side node process that runs an express app. The Angular client was built using Angular 8 with some help from Bootstrap for some quick and simple CSS. The app stores all its data in a MongoDB database which is hosted using the free tier of MongoDB Atlas.
For users to add their cryptocurrency wallets to Wallet QR, they first need to create an account. The signup process requires a username, an email, and a password; a display name can also be given and the username needs to be unique across the website.
New accounts need to validate their email address before they can add wallets to their profile. This is achieved by the user being emailed a single-user URL when they sign up. Once they click the URL in the email their address is verified and they can use all of Wallet QRs functionality.
While viewing their profile, a user can add a new cryptocurrency address to their account. Each wallet requires the wallet address itself, the name of the cryptocurrency, and a user-friendly name for the wallet. Once the wallet is added, any other users who visit their profile can see the wallet and either copy the address or scan the QR code. Users don't need to be logged in to view the profiles of others.
The primary advantage of using Wallet QR is that rather than having to share your wallet addresses with others, you only need to share your username. Usernames are not only easier to remember, but they're also easier to accurately communicate - plus they come with the benefit of representing all of your wallets across all cryptocurrencies.
The web app has been designed for it to be as easy as possible to find the profile that you are looking for. The search bar at the top of the screen searches on keystroke and can be used to look for usernames and display names. The search results come from lookup tables containing all possible substrings of two characters or more allowing matches on substrings found anywhere in results.
The URLs of profiles have also designed to be as concise as possible. Profiles can be found at top level paths prefixed with an @ symbol; meaning that if your username was 'testuser' then the URL for your profile would be
domain.com/@testuser. The use of the @ symbol allows users to have usernames like 'about' or 'contact' should they wish to without conflicting with other pages within the app. This system allows for slightly shorter domains than other techniques like the one implemented by Reddit where profiles are under the '/u/' path.
Passwords are stored using the bcrypt hashing algorithm and go through 10 rounds of salting.
JWTs are used in two different places throughout the website both using HMAC with SHA-256. The first is user sessions; when a user is logged in their user id is stored in a JWT with a 14-day expiry. This JWT is what the client app uses to authenticate with the server process.
The second usage of JWTs is in the email sent to new accounts for them to verify their emails. The single-use URL in this email contains a URL-encoded JWT. This usage not only ties the URL to a specific account via a mechanism that only the server can create, but it also allows the URLs to expire in a stateless manner. The URLs are configured to only be valid for 24 hours. A typical method to achieve this might be to store the URL in the database along with an expiry or creation time. By including the time inside the JWT it means the URLs don't need to be kept in the database.
HaveIBeenPwned.com is a website and service created by Troy Hunt. Using it, anyone can find out if their email address or passwords have been leaked in well-known data breaches. During the signup process for Wallet QR, the web app communicates with the HaveIBeenPwned API securely to detect if the new user's password has been leaked. Using a password that is present in a data breach is dangerous no matter how conventionally secure the password is - this is because breached passwords are some of the first guesses used by hackers.
The communication between Wallet QR and the HaveIBeenPwned API is kept secure using a technique known a K-anonymity. In this process, the web app hashes the password using the SHA-1 algorithm. While SHA-1 is no longer considered secure, it's ideal for this functionality. The first 5 characters of the hash are then sent off to the API; the returned body of the request contains all the hashes of breached passwords where those hashes begin with those 5 characters. It's then up to the web app to iterate over them and determine if the hash of the password is present in the list.
This is a secure process because neither the password plaintext nor the full password hash leaves the web app. The HaveIBeenPwned API is unaware of the full password hash and also never finds out if this queried password was breached or not.