Open Net Worth
Open Net Worth is an open-source personal AI wealth assistant you run yourself: one ledger for your whole financial life, worked by an AI agent you control.
翻訳待ち
No updates detected yet. GitHub releases will appear here.
Experimental. Cashu NFC card client: cryptographic primitives and a Nutshell mint HTTP client.
翻訳待ち
Loading the full README.
Read the full README ↗Cashu NFC card client library for cryptographic primitives and Nutshell mint HTTP requests.
Part of Cashu Card. The card holds Bitcoin-denominated ecash on its own chip and supports spending by tap at the point of sale, without a phone, account or internet connection.
This library is the host half. The card stores proofs and signs. The host library handles blinding, mint communication and verification.
Experimental. Cashu Card is an experimental open source project. It is built on NUT-CCP, a draft NUT proposal that has had little outside review, and its testing so far is limited to dated reports in cashucard/cashucard. Keep only small amounts on a card. Questions go to Discussions.
| To understand… | Read |
|---|---|
| Why the project exists | VISION |
| How the pieces fit | ARCHITECTURE |
| Why the API refuses things | DECISIONS |
| What an attacker can do | SECURITY-MODEL |
| A term you don't know | GLOSSARY |
| Module | Description |
|---|---|
crypto | NUT-00 hash_to_curve, NUT-03 BDHKE blinding/unblinding, NUT-10 P2PK secret serialization, denomination splitting |
card | Reconstructs a spendable proof from a card's 78-byte slot layout using NUT-10 P2PK secret recovery |
cardFile | Defines the interchange format shared by this library and the Python card driver |
mint | Nutshell HTTP client for NUT-01 keysets and NUT-04 mint quotes and proof issuance |
witness | Builds NUT-11 P2PK witnesses from the message a card signs, attaches the signature and verifies the result |
melt | Handles NUT-05 melt quotes, execution and proof selection for card redemption |
swap | Handles NUT-03 change, receiving and unlocking of card-locked proofs |
state | Checks NUT-07 proof state for double spends |
dleq | Verifies offline that a mint signed with its published key using NUT-12 discrete-log equality |
http | Shared URL sanitising and response limits for every mint call |
types | Shared TypeScript types (CashuProof, CashuMeltQuote, CashuProofState, …) |
errors | Typed error classes with numeric codes |
The library now supports the full round trip for P2PK-locked card proofs. Earlier versions could fund a card but could not produce the unlock needed to spend it.
| Stage | Calls |
|---|---|
| Load a card | requestMintQuote → pay → mintProofs → unblindSignature + proofDLEQFromBlindSignature → serializeCardFile → cardctl LOAD_PROOF |
| Verify what's on it | verifyProofDLEQ (offline) or checkProofStates (online) |
| Spend at a terminal | GET_PROOF → parseCardFile → reconstructProofsFromCard → requestMeltQuote → selectProofsForMelt → card signs p2pkMessageToSign → attachP2PKWitness → meltProofs |
| Make change | swapProofs |
The card driver (tools/cardctl)
is dependency-free Python so it runs anywhere a reader does. This library is
TypeScript, and proofs cross the boundary as a file. The cardFile module owns
the schema, and both sides validate against it. A field mismatch fails before
the card can burn a slot with SPEND_PROOF.
{
"version": 1,
"mint": "https://forge.flashapp.me",
"unit": "sat",
"cardPubkey": "032994631ef9a4ba5b0db2f44b4d0d8a4b0eec49bed16091c23c171a8c553a03da",
"slots": [
{
"keysetId": "0059534ce0bfa19a",
"amount": 8,
"nonce": "916c21b8c67da71e9d02f4e3adc6f30700c152e01a07ae30e3bcc6b55b0c9e5e",
"C": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
"spent": false
}
],
"note": "loaded at till 2"
}
import {
serializeCardFile,
parseCardFile,
cardFileTotal,
reconstructProofsFromCard,
sanitizeMintUrl,
} from "@lnflash/cashu-client"
// mint → card: hand this to cardctl to load. `blindingData` is what
// createBlindedMessage returned. It carries the 32-byte nonce the card
// stores. The proof contains no other copy of that nonce.
const json = serializeCardFile({
mint: MINT_URL,
unit: "sat",
cardPubkey,
slots: proofs.map((p, i) => ({
keysetId: p.id,
amount: p.amount,
nonce: blindingData[i].nonce, // the 32-byte nonce; P2PK secret JSON is ~150 bytes
C: p.C,
spent: false, // required; the writer refuses a true
})),
note: "loaded at till 2",
})
// card → mint: what cardctl dumped, ready to spend. Compare against the
// *sanitised* URL. `card.mint` comes back canonical, and a raw constant with
// a trailing slash would reject a card from the right mint.
const card = parseCardFile(await fs.readFile("card.json", "utf8"))
if (card.mint !== sanitizeMintUrl(MINT_URL)) throw new Error("card is from a different mint")
const unspent = card.slots.filter(s => !s.spent)
const spendable = reconstructProofsFromCard(unspent, card.cardPubkey)
console.log(`${cardFileTotal(card)} ${card.unit} across ${unspent.length} slots`)
The wire shape uses the card's vocabulary. It stores nonce and a
16-character hex keysetId. A secret field could carry ~150 bytes of P2PK
JSON into a field that holds 32 bytes, so the parser rejects that field name.
This is the beginning of the README. Read the rest at the original. Read the full README ↗