We Put Paytaca Through an AI-Assisted Security Audit. Here’s What We Found.

In a self-custodial wallet, nothing matters more than how you handle private keys. Mess this up and there's no bank to call, no password reset, no one to reverse a transaction. That's the whole point of self-custody — but it also means we carry a real responsibility to get the fundamentals right.
We’ve seen enough wallet vulnerability reports over the years to know how bad it can get. Weak entropy during wallet generation. Recovery phrases that look fine but are actually predictable. Apps that appear to work perfectly while silently undermining their own security.
So we decided to take a hard look at our own code.
We ran Paytaca’s codebase through an AI-assisted security audit using Kimi K3 and DeepSeek V4 Pro. The goal wasn’t to get AI to sign off on our code. We told it to assume our assumptions were wrong and to find out where they’d break.
It found real things worth fixing.
First we looked at the scariest possibility: weak wallet generation
Everything in a crypto wallet starts with randomness.
When Paytaca creates a wallet, it generates a 12-word BIP39 recovery phrase. Those words encode cryptographic entropy — they’re not just twelve random dictionary words.
We use the bip39 package instead of rolling our own mnemonic generation. Under the hood, bip39.generateMnemonic() uses 128 bits of entropy from crypto.randomBytes. That’s cryptographically secure random generation, not the kind of Math.random() disaster that has broken other wallets.
128 bits means 2^128 possible values. Roughly 340 undecillion possibilities. The security comes from the entropy, not the words themselves.
The most important result of our review was what we didn’t find:
We did not find a weak-entropy wallet generation bug. There’s no catastrophic flaw that undermines every wallet the app creates.
That was the first thing we checked and it was a relief.
But generating keys correctly is only half the problem. What happens to those secrets afterward matters just as much. And that’s where the audit turned up real issues.
Finding #1: Wallet secrets in browser storage weren’t encrypted
Paytaca runs on mobile, web, and as a browser extension. The audit found that on web, one of our secure-storage dependencies could fall back to storing sensitive values as base64 in localStorage.
Base64 is encoding, not encryption. It looks scrambled to a casual user but anyone with browser access can decode it in two seconds.
We fixed this by adding AES-256-GCM encryption through the browser’s Web Crypto API. The encryption key lives separately as a non-extractable CryptoKey in IndexedDB. Existing values get migrated to the encrypted format the next time they’re accessed.
Bottom line: sensitive wallet data in the web and extension versions is now properly encrypted at rest.
Finding #2: Recovery phrases were hanging around in too many places
Encryption matters, but so does reducing how many copies of a secret exist in the first place.
Over time, Paytaca accumulated old and new storage mechanisms for recovery phrases. After migration to the newer scheme, the old representations could stick around.
The audit found these copies. We changed the migration process so that once the wallet confirms the recovery phrase was written successfully with the new storage, the old copy gets removed.
Simple rule: if sensitive data doesn’t need to be somewhere, don’t keep it there.
Finding #3: A legacy PIN mechanism leaked the recovery phrase in metadata
An old PIN-storage mechanism used the mnemonic itself as part of its storage key. Even though the stored value was protected, the recovery phrase was sitting right there in the storage key name — visible to anything that could enumerate storage entries.
That’s gone now. The new implementation uses a SHA-256 hash of the mnemonic instead and deletes the old entry entirely.
Same principle: the recovery phrase should appear in as few places as humanly possible.
Finding #4: Recovery phrases stayed in memory too long
RAM is another place secrets can linger. Wallet objects were cached for the entire lifetime of the application. Recovery phrases and derived key material stayed referenced in memory long after they were needed.
We changed that. When Paytaca locks, when a wallet is deleted, or when certain screens showing the recovery phrase are closed, those references get released so the runtime can reclaim them.
JavaScript can’t securely zeroize strings from memory. That’s a platform limitation. But we can at least stop holding onto secrets indefinitely. Less time in memory means smaller attack surface.
Finding #5: Dead code we should have removed earlier
The audit found an old, unused mechanism for uploading recovery-phrase shards to Paytaca’s Watchtower infrastructure. It wasn’t being used anywhere. But unused code isn’t harmless — it can become an attack surface through bugs, accidental wiring, or future refactors.
So we ripped it out. We also locked down wallet-migration debugging hooks so they don’t leak in production builds.
Less security-sensitive code means less that can go wrong.
Finding #6: Smart-contract signing now verifies internal consistency
Paytaca supports WalletConnect, WizardConnect, and Bitcoin Cash smart contracts. When signing CashScript contract transactions, the wallet received information describing the contract it was being asked to sign.
The audit found that the signing code didn’t independently verify that the supplied redeem script matched the contract encoded in the transaction output being spent.
Note that contract transactions are signed with SIGHASH_UTXOS, so the whole transaction is committed to and nothing can be changed. A malicious dApp presenting one contract while substituting a different script during signing is not really possible.
Still, we kept the suggested change to compare the redeem script with the contract encoded in the transaction output being spent. It now serves as an internal consistency check. We changed the signing process so Paytaca now cryptographically confirms the redeem script matches the script hash in the transaction. For P2SH20 contracts it checks HASH160. For P2SH32 it checks HASH256.
If they don’t match, the wallet refuses to sign. The fix protects against sloppy or inconsistent dApps. It does not, by itself, protect against a dApp that is deliberately malicious.
Closing that gap needs something else: a catalogue of community-vetted contract templates — reviewed by both humans and AI — that WalletConnect and WizardConnect can query. When a dApp asks a user to sign for a contract, the wallet could search that catalogue and compare against known-good code instead of taking the dApp’s word. That’s a real piece of work, and we’re not there yet. What we shipped in this audit is the internal-consistency validation.
Smaller issues too
The audit also found a typo in a binary conversion function and a JavaScript falsy-value edge case in the marketplace escrow address scanner. Individually they look minor. In financial software, small implementation mistakes can cascade.
So we looked at key generation, storage, signing, migration, smart contracts, error handling, memory management — anywhere assumptions about sensitive data could fail.
AI finding problems is a good thing
It might sound weird for a wallet company to publicly discuss security holes in its own software. We see it differently.
I’d be way more worried if an audit of a large evolving codebase found nothing.
Security isn’t achieved by pretending bugs don’t exist. It’s achieved by looking for them aggressively.
An audit should behave differently from a developer. A developer asks “does this work?” A reviewer asks “how can I break this?”
AI is surprisingly good at asking that second question across a lot of code.
What AI changes for a small team
Deep code reviews across an entire application used to be expensive and slow. A small team has limited engineering time.LLMs change the economics. They can trace data flows through a large codebase, spot patterns, inspect crypto operations, question assumptions, find legacy code, and review fixes.
They’re not infallible. They produce false positives. They misunderstand code. They miss things. They don’t replace experienced developers, external researchers, testing, or dedicated security audits.
But that’s the wrong measuring stick. The question isn’t whether AI guarantees security — nobody can guarantee that.
The better question:
Can AI give our team another relentless reviewer that makes it more likely we catch a mistake before an attacker does?
Our experience says yes.
This is now part of how we work
This isn’t a one-time thing. Going forward, every pull request to the Paytaca wallet gets an AI-assisted security review, focused on changes affecting:
- entropy and recovery-phrase generation
- private-key derivation and handling
- recovery-phrase storage
- encryption
- signing
- transaction construction
- smart contracts
- secrets in memory
- sensitive data migration
- external interfaces that could influence signing
AI doesn’t decide if code is secure. It’s another layer in the review process. Humans still make the calls. Automated tests still matter. External audits still matter.
But every change now gets another adversarial pass. For a self-custodial wallet, that’s worth doing.
You can verify everything — we’re open source
Paytaca is open source. You don’t have to take our word for any of this.
The findings, discussion, commits, and fixes are all public:
Pull Request #754:
https://github.com/paytaca/paytaca-app/pull/754
Look at what we found. Look at what we changed. And if you think we missed something, look at the rest of the code too.
That’s why financial infrastructure should be open. Security shouldn’t depend on trusting the company that built it.
Security isn’t a badge you pin on
We’re not publishing this to declare Paytaca “secure.” That’s the wrong takeaway.
Security isn’t a state a project reaches and stays in. Every new feature, dependency, platform, and line of code introduces new assumptions and attack surfaces.
The responsible approach is continuous scrutiny.
Generate keys correctly. Minimize where secrets exist. Encrypt what you must store. Verify instead of trust. Remove unnecessary code. Assume external apps are malicious. Test the fixes.
Then repeat every time the code changes.
Self-custody gives users control of their money. That freedom comes with responsibility for users — and an even bigger one for those of us building the tools they rely on.
We did our homework.
From now on, it’s part of every pull request.