cashucard GitHub
INSTALL GUIDE / APPLET 2.1

Install the Cashu Card applet

This guide puts the Cashu Card applet on a blank NXP J3R180 card from a computer with a card reader, then checks that the card answers. The first card takes about 20 minutes, most of it spent installing tools, and each card after that takes a few minutes.

  • Applet 2.1
  • NXP J3R180 cards
  • GlobalPlatformPro
  • About 20 minutes
  1. 01Install the toolsSet up Java, PC/SC and GlobalPlatformPro.
  2. 02Get the appletDownload one CAP file and check its hash.
  3. 03Check the cardRead it, then log in once.
  4. 04InstallInstall three applets in order.
  5. 05Check the installThe card answers 02 01.
  6. 06Set the card upSet it up before you hand it over.
Contents
  1. What you need
  2. 01 Install the tools
  3. 02 Get the applet
  4. 03 Check the card
  5. 04 Install the applet
  6. 05 Check the install
  7. 06 Set the card up
  8. Reinstall or remove
  9. Troubleshooting
  10. Reference

What you need

  • Blank J3R180 cards Use NXP J3R180 cards (JCOP 4, Java Card 3.0.5) with contact and contactless interfaces, unfused and initialized, on the factory keys 40 to 4F. The shops under Where to get cards sell them.
  • A card reader Any PC/SC reader for ISO 7816-4 cards works, contact or contactless. The project tests with the ACS ACR122U, a USB reader for about $40 to $50, sold on Amazon and by GoToTags.
  • A computer You need a terminal, Git, Java 11 or later and GlobalPlatformPro 20.01.23 or later. Step 1 installs Java and GlobalPlatformPro.

Install the tools

On a Mac, PC/SC ships with the system. Install Java with Homebrew:

Terminal on a Mac
brew install openjdk@17

On Debian or Ubuntu, install the PC/SC daemon and make sure pcscd is running. Install Java 11 or later from your distribution too.

Terminal on Debian or Ubuntu
sudo apt install pcscd libpcsclite-dev

Then install GlobalPlatformPro, the tool that talks to the card's manager. These commands download it and add a gp command:

Terminal
curl -L https://github.com/martinpaljak/GlobalPlatformPro/releases/latest/download/gp.jar \
     -o /usr/local/bin/gp.jar

cat > /usr/local/bin/gp << 'EOF'
#!/bin/sh
exec java -jar /usr/local/bin/gp.jar "$@"
EOF
chmod +x /usr/local/bin/gp

Get the applet

The applet ships as one CAP file, applet version 2.1, at applet/target/cashu-javacard-2.1.0.cap in the project's repository. The file holds three applets: an opener, the card's applet and an NFC tag.

Terminal
git clone https://github.com/cashucard/cashucard
cd cashucard/applet

Check that the file is the published one before you install it:

Terminal, in applet/
sha256sum target/cashu-javacard-2.1.0.cap
Expected
f27d92ba99581f4e42abda0efc038b2c31128f9b405bcec6de52f6a12ae4c7e5  target/cashu-javacard-2.1.0.cap

On a macOS release without sha256sum, shasum -a 256 prints the same line.

Build the CAP yourself

Building needs Apache Ant 1.10 or later, Python 3.9 or later and a Java Card 3.0.5 kit. Fetch the kits once, about 200 MB:

Terminal
git clone https://github.com/martinpaljak/oracle_javacard_sdks ~/.javacard/sdks

Then build in applet/. The build looks for the kit at ~/.javacard/sdks/jc305u4_kit, and -Djc.sdk names another place:

Terminal, in applet/
ant cap
ant cap -Djc.sdk=/path/to/jc305u4_kit

A CAP you build hashes differently from the published one, even from the same source, because the converter writes a timestamp into META-INF/MANIFEST.MF. Compare every other entry with the published file:

Terminal, in applet/
git show HEAD:applet/target/cashu-javacard-2.1.0.cap > /tmp/tracked.cap
rm -rf /tmp/cap-tracked /tmp/cap-built
unzip -q /tmp/tracked.cap -d /tmp/cap-tracked
unzip -q target/cashu-javacard-2.1.0.cap -d /tmp/cap-built
diff -r -x MANIFEST.MF /tmp/cap-tracked /tmp/cap-built && echo "same CAP as the tracked one"
Expected
same CAP as the tracked one

If diff prints a difference, install the published CAP instead.

Check the card

Put a card on the reader. First read what its card manager reports about itself. This command only reads:

Terminal
gp -i -v
Expected on a J3R180, among the rest
Supports SCP02 i=15 i=35 i=55 i=75
GP SCP02 (i=55)

It also lists key set version 01, with three DES3 keys.

Then log in and list what the card holds. With no keys given, gp logs in with the factory keys:

Terminal
gp --list
Expected on a fresh card
ISD: A000000151000000 (OP_READY)

Stop at a refused login. A failed login counts towards a lock that is for good. If the card refuses gp's keys, put that card aside.

Install the applet

The chip takes the card's applet only once another applet from the same package exists, so the install runs in three steps: the small opener first, then the card's applet, then the NFC tag under the NFC Forum's own name.

  1. 1OpenerF04341534855434152444D
  2. 2Card's appletF043415348554341524401
  3. 3NFC tagD2760000850101
Terminal, in applet/
gp --install target/cashu-javacard-2.1.0.cap --applet F04341534855434152444D
gp --create F043415348554341524401 --package F0434153485543415244 --applet F043415348554341524401
gp --create D2760000850101 --package F0434153485543415244 --applet F043415348554341524454

List the card again. The three applets are there, among the rest:

Terminal
gp --list
Expected
APP: F04341534855434152444D (SELECTABLE)   ← the opener
APP: F043415348554341524401 (SELECTABLE)   ← the card's applet
APP: D2760000850101 (SELECTABLE)           ← the tag

Check the install

Select the card's applet by its whole AID. It answers with its version, 2.1, and 9000 for success:

Terminal
gp --apdu 00A404000BF043415348554341524401
Expected
0201 9000

Read its state with GET_INFO. A new card shows 128 empty slots, with its PIN, owner and record still unset:

Terminal
gp --apdu B0010000
Expected
02 01 80 00 00 80 FF 00 04 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
SW: 9000

Read in order: version 2.1, 128 slots, 0 unspent, 0 spent, 128 empty, capabilities FF, no PIN, card file format 4, 3 PIN tries, unlocked, and no record, limit, owner, clock, window or change grant.

Read the card's public key with GET_PUBKEY:

Terminal
gp --apdu B0100000
Expected

A 33-byte compressed secp256k1 public key, then 9000.

Last, read the NFC tag the way a phone does: its name, the capability container and the NDEF file.

Terminal
gp -d -a 00A4040007D2760000850101 -a 00A4000C02E103 -a 00B000000F -a 00A4000C02E104 -a 00B0000002 -a 00B0000214
Expected
9000 / 9000 / 000F20003B00340406E104001600FF 9000 / 9000 / 0014 9000 / D101105504 + "getfoxy.io/card" 9000

When every answer matches, the card is ready to set up.

Set the card up

A card fresh from the install is empty and unowned, and any reader in range can give it a PIN, a record and an owner. Set it up before it leaves your hands.

When you give a card to someone, they set it up from their own phone, in a wallet that supports Cashu Card, with Set up this card. The wallets are listed under Apps at the foot of this page.

Set up a bench card with cardctl

cardctl, the project's command-line driver, needs Python 3 with pyscard. On a Mac, use a Homebrew Python, because the system one cannot load pyscard. From the repository's root folder:

Terminal on a Mac
cd tools/cardctl
brew install python@3.13
python3.13 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

Then set the card up, owner last, with the tip block header as the anchor:

Terminal, in tools/cardctl
python3 cardctl.py set-pin 1234
python3 cardctl.py verify-pin 1234
python3 cardctl.py set-card --mint <your mint's URL>
python3 cardctl.py set-owner --owner-key <32-byte hex> --anchor <the tip header, 80 bytes hex>
python3 cardctl.py set-limit --owner-key <…> --day 50000 --payment 0 --allowance 0 --budget 7
python3 cardctl.py selftest

Use this on bench cards. When --owner-key is left out, cardctl signs with its TEST key, which anyone with the repository holds.

cardctl selftest passes a card that answers 02 01 or a later 2.x version.

Reinstall or remove

Spend the balance first. Every piece on a card is locked to the card's key, and a reinstall makes a new key. Spend a card's balance before you delete its applet.

Delete the instances one at a time, the tag first and the package last. The chip refuses to delete an instance that something still refers to, which sets the order:

Terminal
gp --delete D2760000850101           # the tag
gp --delete F043415348554341524401   # the card's applet
gp --delete F04341534855434152444D   # the opener
gp --delete F0434153485543415244     # the package

Then install again, the opener first and the tag last:

Terminal, in applet/
gp --install target/cashu-javacard-2.1.0.cap --applet F04341534855434152444D
gp --create F043415348554341524401 --package F0434153485543415244 --applet F043415348554341524401
gp --create D2760000850101 --package F0434153485543415244 --applet F043415348554341524454

Troubleshooting

gp --install fails with 6F00
Check the order in step 4: the chip takes the card's applet only after the opener. With the order right, suspect the size of the load file next, then the applet's install-time check of how the chip frames its ECDH output.
SELECT answers 6A82
The card has an earlier applet, 0.x or 1.x, and needs an update.
SELECT answers 9000 with no version
That SELECT named the package AID alone, F0434153485543415244, which reaches the opener, and the opener answers 6D00 to every command after it. Select the whole AID, F043415348554341524401, as in step 5.
The card refuses gp's keys
Put the card aside, as step 3 explains.
The CAP will not load on a Java Card 3.0.4 card
The applet signs with KeyAgreement.ALG_EC_SVDP_DH_PLAIN_XY, which arrived in Java Card 3.0.5, so a 3.0.4 card cannot run it. Use a J3R180.
cardctl stops with ImportError: dynamic module does not define module export function (PyInit__scard)
That is the macOS system Python. Use a Homebrew Python, as the bench setup in step 6 does.

Reference

Applet IDs

ElementAID (hex)
PackageF0 43 41 53 48 55 43 41 52 44
Opener, made firstF0 43 41 53 48 55 43 41 52 44 4D
Card's appletF0 43 41 53 48 55 43 41 52 44 01
Tag, class (listed last in the file)F0 43 41 53 48 55 43 41 52 44 54
Tag, instance (the NFC Forum's NDEF Tag Application, which a phone selects)D2 76 00 00 85 01 01
SELECT of the card's applet, by its whole AID00 A4 04 00 0B F0 43 41 53 48 55 43 41 52 44 01
SELECT of the package AID alone, which reaches the opener00 A4 04 00 0A F0 43 41 53 48 55 43 41 52 44

Supported cards

CardJava CardStatus
NXP JCOP4 SmartMX3 P71 (J3R180), from any seller3.0.5Primary target, with the secp256k1 custom curve supported
Generic Java Card 3.0.5 or later3.0.5May work; check its custom EC-FP curve and ALG_EC_SVDP_DH_PLAIN_XY support first
Feitian's own Java Card 3.0.4 cards3.0.4Cannot run it: it lacks ALG_EC_SVDP_DH_PLAIN_XY, so the CAP will not convert or load
Java Card 3.0.4 or earlier3.0.4 and earlierCannot run it: no ECDH plain-XY
Java Card 2.2.x2.2Cannot run it: no int type and no ECDH plain-XY

More detail