Skip to content

Enhancement: Migrate to native Ed25519 signatures and public-key address derivation (#31) - #32

Merged
techcsispit merged 3 commits into
techcsispit:mainfrom
sohanjadyal:issue-31
Oct 19, 2026
Merged

techcsispit merged 3 commits into
techcsispit:mainfrom
sohanjadyal:issue-31

Conversation

@sohanjadyal

@sohanjadyal sohanjadyal commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Closes #31

Note on PR Ordering & serialVersionUID

Note

This PR is expected to be merged after PR #30.

  • PR Enhancement: Deterministic transaction hashing across all record fields (#29) #30 bumps Block.serialVersionUID from 3L to 4L.
  • Accordingly, this PR bumps Block.serialVersionUID to 5L (and updates README.md) to avoid serial version conflicts and ensure that chains saved under older formats (or pre-Ed25519 versions) trigger an InvalidClassException and start fresh rather than failing validation silently.

What I changed

  • Migrated to Native Java 17 Ed25519 Signatures:
    • Replaced legacy SHA256withRSA (1024-bit RSA) with native Ed25519 (EdDSA) in Transaction.create() and Transaction.hasValidSignature().
    • Replaced RSA key generation in Main.getWallet() with native KeyPairGenerator.getInstance("Ed25519") (zero third-party dependencies).
  • Cryptographic Address Derivation & Anti-Front-Running:
    • Added Transaction.deriveAddress(PublicKey) and Blockchain.deriveAddress(PublicKey) to derive account addresses via SHA-256 hex encoding of encoded public keys.
    • Enforced in Blockchain.addTransaction() that tx.from() must strictly match deriveAddress(tx.senderKey()), preventing username squatting and front-running identity theft.
    • Enforced address derivation verification across mined blocks in Blockchain.isValid().
  • Recipient Wallet Handling & Address Resolution in CLI:
    • Updated CLI (Main.java) address resolution so users can continue interacting with friendly aliases ("asha", "ravi") while underlying transactions bind cryptographically to derived public-key addresses.
    • Automatically initializes and persists recipient wallets in wallets.dat when sending coins to a friendly name so funds remain spendable.
    • Reverse-resolves addresses in menu Option 4 (Show balances) so friendly names are displayed when known.
  • Bumped Block.serialVersionUID to 5L & Updated README.md:
    • Documented under Saving that Block's serialVersionUID is 5L for Ed25519 signatures and address derivation.
  • Updated & Added Tests:
    • Updated BlockchainTest.java, SignatureTamperingTest.java, and ReadmeRulesTest.java to use Ed25519.
    • Added unit tests in BlockchainTest.java:
      • validEd25519SigningAndVerification: Verifies 64-byte Ed25519 signatures and tamper detection.
      • rejectionOfTransactionsWhereFromDoesNotMatchDerivedAddress: Rejects transactions with arbitrary names or mismatched public keys.
      • frontRunningProtectionAcrossAccounts: Verifies attacker cannot claim or spend another account's balance.

How it works

Previously, mini-chain allowed accounts to register arbitrary human-readable string aliases (e.g., "Alice", "Asha"). In Blockchain.java, the chain associated an arbitrary username with whichever public key was submitted first in knownKeys. A malicious actor observing pending transactions could front-run a victim's chosen name with their own key, permanently locking the victim out. In addition, 1024-bit RSA keys and signatures were bulky, slow, and cryptographically deprecated.

This PR migrates key generation and signing to native Java 17 Ed25519 (EdDSA) with zero external dependencies, producing compact 32-byte public keys and 64-byte signatures. Account addresses are derived deterministically from public keys via a SHA-256 digest of key.getEncoded(). In Blockchain.addTransaction() and Blockchain.isValid(), tx.from() must strictly match the derived address of tx.senderKey(), eliminating front-running and username squatting entirely. In the CLI (Main.java), aliases are mapped locally in wallets.dat and recipient keypairs are automatically generated on first transfer so funds sent to any friendly name remain spendable.

Verification

All 36 automated unit and integration tests pass cleanly with mvn clean test:

Verification:

  1. Automated Test Suite:
  • Run mvn clean test
image image

Runs all 36 tests across BlockchainTest, SignatureTamperingTest, and ReadmeRulesTest.

  1. Interactive CLI Walkthrough:
  • Run mvn compile exec:java and follow the steps given below:
  • Option 2 (Mine a block): Enter asha -> generates an Ed25519 wallet in wallets.dat and awards 50 coins to her derived address.
  • Option 4 (Show balances): Displays asha: 50.
  • Option 1 (Send coins): From asha to ravi, amount 20 -> signs with asha's Ed25519 key, auto-creates ravi's wallet, and queues payment.
  • Option 2 (Mine a block): Enter miner -> confirms the block.
  • Option 4 (Show balances): Displays asha: 30, ravi: 20, and miner: 50.
  • Option 6 (Is the chain valid?): Displays The chain is valid..
  • Option 7 (Tamper with a block): Choose block 2 to alter payment amount and re-mine block hash.
  • Option 6 (Is the chain valid?): Displays The chain has been tampered with. (caught by Ed25519 digital signature verification).

@techcsispit

Copy link
Copy Markdown
Owner

#30 is merged, please rebase onto main. Existing wallets.dat files hold RSA keys from #25, and signing with one now throws InvalidKeyException. Please migrate old wallets or discard them cleanly, and add a test for it. Also please say why the crypto change, address derivation and CLI redesign belong in one PR.

@sohanjadyal

Copy link
Copy Markdown
Contributor Author

Okay, I'll make the required changes

@sohanjadyal

sohanjadyal commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor Author

@techcsispit Rebased on latest main (including #30), fixed the conflicts, and tackled the comments:

Legacy wallets.dat cleanup
Storage.loadWallets(...) now checks the key algorithm and drops old RSA keys from #25 with a log message (same way Storage.load(...) handles incompatible chains). If Main.getWallet(...) hits an old key, it'll just generate a fresh Ed25519 keypair. Also added oldRsaWalletsAreDiscardedCleanly and mixedWalletsDiscardRsaKeysAndKeepEd25519Keys to verify old keys get pruned cleanly without blowing up with an InvalidKeyException when signing.

On keeping the crypto migration, address derivation, and CLI changes in one PR
I kept these together because splitting them up leaves the repo in a weird intermediate state:

  1. Identity + Crypto: Tying addresses to public keys (SHA-256(publicKey)) fixes the old issue where accounts were just unverified strings ("asha", "ravi"). Doing address derivation on RSA first would've meant generating addresses from variable-length RSA keys, only to immediately break and replace them when switching to Ed25519.

  2. Single protocol bump: Both the Ed25519 switch and the deriveAddress(senderKey) == tx.from() check break block validation and bump Block.serialVersionUID to 5L. Bundling them avoids breaking the chain twice in a row.

  3. CLI UX: 64-char hex addresses aren't really usable from the command line without an alias/wallet manager to map friendly names under the hood. Shipping the address change alone breaks the CLI workflow, and the CLI alias changes don't make much sense without the hex addresses they're meant to wrap.

@techcsispit
techcsispit merged commit ccd4d84 into techcsispit:main Oct 19, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhancement: Migrate to native Ed25519 signatures and public-key address derivation

2 participants