Skip to content

tests: add NUT-28 HTLC example proof - #402

Open
robwoodgate wants to merge 2 commits into
cashubtc:mainfrom
robwoodgate:nut28-htlc-example
Open

tests: add NUT-28 HTLC example proof#402
robwoodgate wants to merge 2 commits into
cashubtc:mainfrom
robwoodgate:nut28-htlc-example

Conversation

@robwoodgate

@robwoodgate robwoodgate commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Adds an HTLC example to the NUT-28 test vectors, using the same fixed keypairs as the P2BK P2PK example.

Tests pinned in:

It pins the blinding slot order for HTLC secrets: the hash lock occupies slot 0 unblinded, the pubkeys key blinds at slot 1 and the refund key at slot 2. The locktime tag sits between them in the JSON as a check that slots are numbered by the canonical [data, ...pubkeys, ...refund] order, not by tag position. This mirrors the fix in cashubtc/cashu-ts#763, where keys were previously blinded from slot 0.

The blinded keys are the existing slot 1 and 2 table entries, so the example is cross-checkable against the published vectors. The hash lock reuses the documented NUT-14 preimage pair. C and dleq are internally consistent (DLEQ verifies via reblind).

Rationale

Cashu-TS had a bug where slot 0 was ignored in HTLC. This test prevents other implementations doing the same.
Vectors confirmed and pinned in cashubtc/cashu-ts#786

Illustrates the blinding slot order for HTLC secrets: the hash lock
occupies slot 0 unblinded, the pubkeys key blinds at slot 1 and the
refund key at slot 2. Blinded keys match the existing slot tables and
the hash lock reuses the NUT-14 preimage pair.
Encodes the two example proofs as a V4 token, pinning the pe field
(ephemeral pubkey E as a 33-byte CBOR bstr) and dleq wire format.
@robwoodgate robwoodgate added the documentation Improvements or additions to documentation label Jul 14, 2026
robwoodgate added a commit to cashubtc/cashu-ts that referenced this pull request Jul 16, 2026
## Summary

Pins the NUT-28 test vectors (cashubtc/nuts#402) in three layers, so a
symmetric sender/receiver drift cannot pass by self-consistency:

- **Unit** (`test/crypto/NUT28.test.ts`): the P2PK and HTLC example
proofs verbatim, through `maybeDeriveP2BKPrivateKeys`, `signP2PKProof`
and spend verification. The HTLC proof pins the slot order fixed in
#763: hashlock at slot 0 unblinded, `pubkeys` key at slot 1, `refund`
key at slot 2, spendable via both pathways.
- **Token** (`test/utils/core.test.ts`): the example V4 token decodes to
the exact proofs (including `dleq` and `p2pk_e`) and re-encodes
byte-for-byte, pinning the `pe`/`d` wire format against cross-wallet
drift. The vector's CBOR was verified independently of this library.
- **Integration** (`test/integration.test.ts`): new `send and receive
p2bk HTLC` round trip (mint, HTLC blindKeys send, receive with privkey
plus preimage witness). The shared `expectP2BKLockedToBob` helper
replaces the hand-rolled ECDH check and uses `deriveP2BKSecretKeys`
directly, which has no legacy slot 0 fallback, so a sender regression to
slot 0 blinding fails the assertion even though receive would still work
via the fallback.

Also drops a DLEQ reblind check that belonged to NUT-12, not P2BK.

## Tests

`npm run prtasks` passes (195 files). Full integration suite passes
against a fresh CDK 0.17.2 mint (46/46).
@robwoodgate

Copy link
Copy Markdown
Collaborator Author

@ye0man - this should be a shoo-in. Be good to get the vector into CDK / Nutshell for completeness

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

1 participant