lnurlcash-conformance 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,7 +4,53 @@ Semantic versioning. While the LUD-25 draft is unmerged, `0.x` minor bumps
4
4
  may add or tighten checks that a previously-passing mint now fails; pin an
5
5
  exact version if you gate CI on the grade.
6
6
 
7
- ## Unreleased
7
+ ## 0.8.0 - 2026-09-09
8
+
9
+ **A spent note must say so.** LUD-25 [#307][307] settled the hash lookup's
10
+ status contract the other way round from 0.7.0: a retained burned hash returns
11
+ a spent error, an unregistered hash stays unknown, and the two must be
12
+ distinguishable. A holder who kept only the hash can then learn a note was
13
+ redeemed without putting its bearer secret on the wire, which is the whole
14
+ point of the lookup. Mints passing 0.7.0's privacy check fail this one, and the
15
+ change is deliberate.
16
+
17
+ [307]: https://github.com/lnurl/luds/pull/307
18
+
19
+ - Revise spent-hash grading to match the proposed LUD-25 status contract:
20
+ a retained burned hash must return a spent error, while an unregistered
21
+ hash remains unknown. Add the adversarial `hidesSpent` fixture; retain
22
+ `revealsSpent` as a compliant compatibility alias. This reverses the
23
+ 0.7.0 privacy check and requires corresponding mint changes.
24
+
25
+ - Documentation only, no grade changes. `previousPubkeys` is no longer a
26
+ LUD-25 field: the spec carried it briefly and dropped it on 2026-09-04,
27
+ replacing it with "`SERVICE` SHOULD NOT rotate `mintPubkey`" plus a
28
+ `WALLET`-side MUST to pin per origin and require explicit holder approval
29
+ for any replacement. The runner already graded it for shape only and never
30
+ for presence, which is the right posture for a convention; the comments and
31
+ the flag table now say that is what it is, and that a wallet must not read
32
+ it as permission to move a pin by itself.
33
+
34
+ ## 0.7.0 - 2026-09-04
35
+
36
+ **`cash-derivation.json`: LUD-25's own seed-recoverable note secrets.** The
37
+ draft specifies a BIP-32 scheme under `m/139'` and the reference wallet
38
+ implements it; `derivation.json` remains, unchanged, as the pre-spec HMAC
39
+ scheme that notes are still outstanding under.
40
+
41
+ Every case carries `cashRoot`, the four `domainIndices`, a `hardened` flag per
42
+ index, the `domainNode` and the resulting `k1`. The flags are the point of the
43
+ file. `d1..d4` are raw uint32 and BIP-32 reads any index `>= 2^31` as hardened,
44
+ so which levels are hardened is decided by the mint's own host name - an
45
+ implementation that masks the top bit, or hardens all four, derives a
46
+ different tree and restores nothing, silently. Both hosts in the vector land
47
+ on a mix, so an implementation that gets it wrong cannot pass by luck.
48
+
49
+ `bip32Vector1` carries BIP-32's own published test vector 1, so a port can
50
+ prove its CKDpriv before blaming the LUD-25 path above it. The generator
51
+ implements BIP-32 from `@noble` primitives rather than importing a BIP-32
52
+ library, for the reason at the top of `tools/generate.mjs`: a vector produced
53
+ by the library under test proves nothing.
8
54
 
9
55
  ## 0.6.0 - 2026-09-03
10
56
 
package/README.md CHANGED
@@ -104,7 +104,7 @@ must survive:
104
104
  | `--retriedMutation=refuse` | deliberately answers an identical mutation retry as already spent instead of replaying its original success |
105
105
  | `--hashLookup=echoesK1` | offers hash lookup but puts a `k1` back in the response |
106
106
  | `--hashLookup=answersUnknown` | offers hash lookup but invents a note for an unknown hash |
107
- | `--hashLookup=revealsSpent` | offers hash lookup but distinguishes a burned hash from an unknown note |
107
+ | `--hashLookup=hidesSpent` | incorrectly reports a retained spent hash as unknown |
108
108
  | `--hashLookup=acceptsBoth` | offers hash lookup but accepts `k1` and `h` together |
109
109
  | `--mintToHashAcceptsMalformedH` | claims `mintToHash` and invoices an `h` that is not 64 lowercase hex, so a wallet pays for a quote the mint will refuse |
110
110
  | `--mintToHashAcceptsUsedH` | claims it and invoices an `h` that already names a note, an invoice or another quote's output |
@@ -114,6 +114,10 @@ The three `mintToHash*` misbehaviours need `--mintToHash` alongside them;
114
114
  on their own they do nothing, because a mint that never offered the
115
115
  capability cannot misuse it.
116
116
 
117
+ Hash lookup keeps the spending secret off the wire but reports spent state:
118
+ `--hashLookup=true` distinguishes a burned hash from an unknown hash. The older
119
+ `--hashLookup=revealsSpent` spelling remains an alias for this compliant behaviour.
120
+
117
121
  The remaining flags enable optional features, legal wire variants, or make a
118
122
  conforming default explicit. Optional fields stay absent unless requested:
119
123
 
@@ -125,7 +129,7 @@ conforming default explicit. Optional fields stay absent unless requested:
125
129
  | `--baseFeeMsat --feePpm` | also publishes `fees: {baseFeeMsat, feePpm}` on that endpoint, the structured twin of the fee line in the payRequest metadata |
126
130
  | `--stats` | serves `GET /stats`: what the mint owes, what is in flight, what the node holds, and the coverage between them |
127
131
  | `--localBalanceMsat=N` | what the node behind a stats-publishing mock claims to hold, so a mock can be told to look under-covered |
128
- | `--previousPubkeys=a,b` | keys this mint has signed under before, so notes issued before a rotation still verify |
132
+ | `--previousPubkeys=a,b` | keys this mint has signed under before, so notes issued before a rotation still verify. Not a LUD-25 field: the spec carried it briefly and dropped it on 2026-09-04. Graded for shape where a mint offers it, never asked for |
129
133
  | `--previousPrivateKey=<hex>` | an old signing key the mock still holds. Its public half joins `previousPubkeys` on its own |
130
134
  | `--signWithPreviousKey` | issues every note under that old key while still advertising the new one: the mid-rotation state a mint passes through when the advertisement moves before the signer |
131
135
  | `--retriedMutation=replay` | answers a byte-identical repeat of a mutation with the original success. This is the conforming default; use `refuse` only as an adversarial fixture |
@@ -268,7 +272,7 @@ Spec and reference implementations, all by dni, all MIT:
268
272
  - [lnurl-wallet](https://github.com/dni/lnurl-wallet) — the reference wallet
269
273
 
270
274
  Implementations to run these vectors against are indexed in
271
- [awesome-lnurlcash](https://github.com/TheCryptoDonkey/awesome-lnurlcash).
275
+ [awesome-lnurlcash](https://github.com/lnurlcash/awesome-lnurlcash).
272
276
 
273
277
  Contributions of vectors are welcome, particularly from implementers who
274
278
  found a case these missed. See [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -14,6 +14,8 @@ export type HashLookup =
14
14
  | boolean
15
15
  | 'echoesK1'
16
16
  | 'answersUnknown'
17
+ | 'hidesSpent'
18
+ /** Legacy alias for true: spent hashes are now distinguishable. */
17
19
  | 'revealsSpent'
18
20
  | 'acceptsBoth'
19
21
 
@@ -190,8 +190,8 @@ const DEFAULTS = {
190
190
  // on the wire the lookup existed to keep it off
191
191
  // 'answersUnknown' - non-compliant: answers for a hash it never
192
192
  // registered, instead of the unknown-note refusal
193
- // 'revealsSpent' - non-compliant: distinguishes a burned h from an
194
- // unknown note
193
+ // 'hidesSpent' - non-compliant: hides a retained burned h as unknown
194
+ // 'revealsSpent' - legacy alias for the now-compliant true behaviour
195
195
  // 'acceptsBoth' - non-compliant: accepts k1 and h together
196
196
  hashLookup: false,
197
197
  // LUD-25 lets a SERVICE refuse an oversized merge outright rather than
@@ -813,8 +813,9 @@ export const createMockMint = async (options = {}) => {
813
813
  if (!invent) {
814
814
  if (!held) return fail('Unknown note.')
815
815
  if (held.state === 'burned') {
816
- return fail(opts.hashLookup === 'revealsSpent' ? 'Note already spent.' : 'Unknown note.')
816
+ return fail(opts.hashLookup === 'hidesSpent' ? 'Unknown note.' : 'Note already spent.')
817
817
  }
818
+ if (held.state === 'pending') return fail('pending')
818
819
  }
819
820
  return send({
820
821
  tag: 'withdrawRequest',
@@ -833,6 +834,7 @@ export const createMockMint = async (options = {}) => {
833
834
  const note = notes.get(noteId(k1))
834
835
  if (!note) return fail('Unknown note.')
835
836
  if (note.state === 'burned') return fail('Note already spent.')
837
+ if (note.state === 'pending') return fail('pending')
836
838
  return send({
837
839
  tag: 'withdrawRequest',
838
840
  callback: `${origin}/w/cb`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lnurlcash-conformance",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Language-neutral conformance vectors, an adversarial mock mint, and a grader for LNURLcash (LUD-25) implementations",
5
5
  "author": "TheCryptoDonkey",
6
6
  "license": "MIT",
@@ -9,11 +9,11 @@
9
9
  },
10
10
  "repository": {
11
11
  "type": "git",
12
- "url": "git+https://github.com/TheCryptoDonkey/lnurlcash-conformance.git"
12
+ "url": "git+https://github.com/lnurlcash/lnurlcash-conformance.git"
13
13
  },
14
- "homepage": "https://github.com/TheCryptoDonkey/lnurlcash-conformance#readme",
14
+ "homepage": "https://github.com/lnurlcash/lnurlcash-conformance#readme",
15
15
  "bugs": {
16
- "url": "https://github.com/TheCryptoDonkey/lnurlcash-conformance/issues"
16
+ "url": "https://github.com/lnurlcash/lnurlcash-conformance/issues"
17
17
  },
18
18
  "type": "module",
19
19
  "keywords": [
package/runner/index.mjs CHANGED
@@ -364,6 +364,15 @@ export const gradeMint = async (payUrl, report) => {
364
364
  // absence is never a failure - but a field that IS published and is
365
365
  // the wrong shape is worth saying out loud, because a wallet will try
366
366
  // to render it. Malformed means a warning, not a fail.
367
+ //
368
+ // previousPubkeys in particular: LUD-25 carried it briefly and dropped
369
+ // it on 2026-09-04, replacing it with "SERVICE SHOULD NOT rotate
370
+ // mintPubkey" plus a WALLET-side MUST to pin per origin and require
371
+ // explicit holder approval for any replacement. So it is now purely a
372
+ // convention between the implementations that already emit and read it,
373
+ // and it is graded here as one: shape only, never presence. Nothing in
374
+ // this suite asks a SERVICE to publish it, and a wallet must not treat
375
+ // it as permission to move a pin by itself.
367
376
  const problems = []
368
377
  for (const key of ['name', 'description', 'tosUrl', 'motd', 'version']) {
369
378
  const value = body[key]
@@ -833,7 +842,10 @@ export const gradeBoundMint = async (noteUrl, report, {preimage, payCallback = n
833
842
  // options.previousPubkeys: keys this mint has signed under before, from
834
843
  // the discovery endpoint. A note issued before a signing-key rotation
835
844
  // still verifies against one of them, and grading that as a bad signature
836
- // would punish a mint for rotating properly.
845
+ // would punish a mint for rotating properly. Not a LUD-25 field - the spec
846
+ // dropped it on 2026-09-04 and now says a SERVICE SHOULD NOT rotate at all
847
+ // - so this is a convention the grader honours where a mint offers it,
848
+ // never something it asks for.
837
849
  export const gradeNote = async (noteUrl, report, options = {}) => {
838
850
  const knownBaseFee = 'mintFee' in options ? (options.mintFee?.baseFeeMsat ?? 0) : null
839
851
  const previousPubkeys = Array.isArray(options.previousPubkeys)
@@ -1038,9 +1050,12 @@ export const gradeNote = async (noteUrl, report, options = {}) => {
1038
1050
  assert(info.mintPubkey, 'no mintPubkey advertised - offline verification is mandatory')
1039
1051
  assert(isCompressedPubkey(info.mintPubkey), 'mintPubkey is not a 33-byte compressed secp256k1 key')
1040
1052
  assert(currentSig, 'the rotate returned no sig - offline verification is mandatory')
1041
- // A mint that has rotated its signing key publishes the old ones as
1053
+ // A mint that has rotated its signing key may publish the old ones as
1042
1054
  // previousPubkeys, so notes it issued before the rotation still
1043
- // verify. Any key it currently stands behind is an acceptable signer.
1055
+ // verify. Any key it currently stands behind is an acceptable signer
1056
+ // for grading purposes. That is a narrower claim than it looks: it
1057
+ // says the signature is genuine, not that a wallet should accept the
1058
+ // new key - LUD-25 puts that decision with the holder.
1044
1059
  const signedBy = [info.mintPubkey, ...previousPubkeys].find(key =>
1045
1060
  verifySignature(current, info.maxWithdrawable, currentSig, key)
1046
1061
  )
@@ -1055,26 +1070,28 @@ export const gradeNote = async (noteUrl, report, options = {}) => {
1055
1070
  : `verified offline against a previous signing key (${signedBy.slice(0, 16)}...)`
1056
1071
  })
1057
1072
 
1058
- await report.check('does not reveal a burned note through hash lookup', async () => {
1073
+ await report.check('reports a spent hash distinguishably from an unknown hash', async () => {
1059
1074
  if (!hashLookupOffered) throw soft('hash lookup is not offered')
1060
1075
  const burned = new URL(url)
1061
1076
  burned.searchParams.delete('k1')
1062
1077
  burned.searchParams.delete('amount')
1063
1078
  burned.searchParams.set('h', noteId(k1))
1064
1079
  const burnedResponse = await get(burned)
1080
+ assert(
1081
+ burnedResponse.status === 'ERROR' && typeof burnedResponse.reason === 'string' &&
1082
+ /spent/i.test(burnedResponse.reason) && !/unknown|not found/i.test(burnedResponse.reason),
1083
+ `a spent hash must be identified as spent, got ${JSON.stringify(burnedResponse)}`
1084
+ )
1065
1085
 
1066
- const unknown = new URL(url)
1067
- unknown.searchParams.set('k1', bytesToHex(randomBytes(32)))
1068
- unknown.searchParams.delete('h')
1069
- unknown.searchParams.delete('amount')
1086
+ const unknown = new URL(burned)
1087
+ unknown.searchParams.set('h', noteId(bytesToHex(randomBytes(32))))
1070
1088
  const unknownResponse = await get(unknown)
1071
-
1072
1089
  assert(
1073
- burnedResponse.status === unknownResponse.status &&
1074
- burnedResponse.reason === unknownResponse.reason,
1075
- `a burned h answered ${JSON.stringify(burnedResponse)} but an unknown k1 answered ${JSON.stringify(unknownResponse)}`
1090
+ unknownResponse.status === 'ERROR' && typeof unknownResponse.reason === 'string' &&
1091
+ /unknown|not found/i.test(unknownResponse.reason) && !/spent/i.test(unknownResponse.reason),
1092
+ `an unregistered hash must be identified as unknown, got ${JSON.stringify(unknownResponse)}`
1076
1093
  )
1077
- return 'burned h is indistinguishable from an unknown note'
1094
+ return 'spent and unknown hashes are distinguishable without disclosing k1'
1078
1095
  })
1079
1096
 
1080
1097
  await report.check('keeps signatures off the informational endpoint', async () => {
@@ -0,0 +1,160 @@
1
+ {
2
+ "version": 1,
3
+ "spec": "LUD-25 draft (lnurl/luds#301)",
4
+ "description": "LUD-25's specified seed-recoverable note secrets, the BIP-32 scheme under m/139'. cashRoot is m/139' as privateKey||chainCode hex (64 bytes, no version/depth/fingerprint framing). domainIndices are the four raw uint32 read big-endian from the first 16 bytes of HMAC-SHA256(key = the private key at m/139'/0, msg = utf8(host)); they are used as BIP-32 child indices exactly as they fall, so `hardened` records which of them land >= 2^31 by magnitude - an implementation that masks the top bit or hardens all four derives a different tree and will restore nothing. domainNode is m/139'/d1/d2/d3/d4. k1 is the private key at the hardened child `index` of that node, 32 bytes lowercase hex, and the mint sees only sha256(k1) as ever. host is the mint host exactly as the wallet stores it, with the port when there is one. seedHex is the 64-byte BIP39 seed with no passphrase. bip32Vector1 is BIP-32's own published test vector 1, so CKDpriv itself can be checked first.",
5
+ "scheme": {
6
+ "purpose": "m/139'",
7
+ "hashingKey": "m/139'/0",
8
+ "domainMsg": "host",
9
+ "secretPath": "m/139'/d1/d2/d3/d4/i'",
10
+ "hardenedByMagnitudeOnly": true
11
+ },
12
+ "bip32Vector1": [
13
+ {
14
+ "index": null,
15
+ "node": "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508"
16
+ },
17
+ {
18
+ "index": 2147483648,
19
+ "hardened": true,
20
+ "node": "edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141"
21
+ },
22
+ {
23
+ "index": 1,
24
+ "hardened": false,
25
+ "node": "3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc933682a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19"
26
+ },
27
+ {
28
+ "index": 2147483650,
29
+ "hardened": true,
30
+ "node": "cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f"
31
+ },
32
+ {
33
+ "index": 2,
34
+ "hardened": false,
35
+ "node": "0f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd"
36
+ },
37
+ {
38
+ "index": 1000000000,
39
+ "hardened": false,
40
+ "node": "471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e"
41
+ }
42
+ ],
43
+ "cases": [
44
+ {
45
+ "name": "standard mnemonic, index 0",
46
+ "mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
47
+ "seedHex": "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4",
48
+ "host": "mint.example",
49
+ "index": 0,
50
+ "cashRoot": "c7a2496e9b453a67c5d2a1f04936ec1259440d45454c795a99a66269e4cd3005111e1cc966fca2fe32f054f14caceab90449e536d94cf6935ea12a087e414f60",
51
+ "domainIndices": [
52
+ 2589708612,
53
+ 3693348916,
54
+ 172082394,
55
+ 3793182078
56
+ ],
57
+ "hardened": [
58
+ true,
59
+ true,
60
+ false,
61
+ true
62
+ ],
63
+ "domainNode": "72056e5cde21458b13689c3950904dfd327415a506d064290e5e5f4296a40543dd5e9504ddb6eefbafa4ad3b00ad421858fafc0ed9ea4abd9cb68793f845cfc1",
64
+ "k1": "de5b81405a12e1297b350d80e2ad85043ed5b9436a0c5592d3302778de330499",
65
+ "noteId": "7db9da2845cd45c1c3c2e302d6135da46823e245f756b830ef59ac324b769e02"
66
+ },
67
+ {
68
+ "name": "standard mnemonic, index 1",
69
+ "mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
70
+ "seedHex": "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4",
71
+ "host": "mint.example",
72
+ "index": 1,
73
+ "cashRoot": "c7a2496e9b453a67c5d2a1f04936ec1259440d45454c795a99a66269e4cd3005111e1cc966fca2fe32f054f14caceab90449e536d94cf6935ea12a087e414f60",
74
+ "domainIndices": [
75
+ 2589708612,
76
+ 3693348916,
77
+ 172082394,
78
+ 3793182078
79
+ ],
80
+ "hardened": [
81
+ true,
82
+ true,
83
+ false,
84
+ true
85
+ ],
86
+ "domainNode": "72056e5cde21458b13689c3950904dfd327415a506d064290e5e5f4296a40543dd5e9504ddb6eefbafa4ad3b00ad421858fafc0ed9ea4abd9cb68793f845cfc1",
87
+ "k1": "267570df5ba8098d728e839a698f729c2df6fa7b8b7ae7c9c7ffa7dda3417e1d",
88
+ "noteId": "99b98cb845940e0d6401e176aaca1446365aa315201803861e17f3c530ed1dcf"
89
+ },
90
+ {
91
+ "name": "standard mnemonic, index 20 (one past a 20-index gap limit)",
92
+ "mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
93
+ "seedHex": "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4",
94
+ "host": "mint.example",
95
+ "index": 20,
96
+ "cashRoot": "c7a2496e9b453a67c5d2a1f04936ec1259440d45454c795a99a66269e4cd3005111e1cc966fca2fe32f054f14caceab90449e536d94cf6935ea12a087e414f60",
97
+ "domainIndices": [
98
+ 2589708612,
99
+ 3693348916,
100
+ 172082394,
101
+ 3793182078
102
+ ],
103
+ "hardened": [
104
+ true,
105
+ true,
106
+ false,
107
+ true
108
+ ],
109
+ "domainNode": "72056e5cde21458b13689c3950904dfd327415a506d064290e5e5f4296a40543dd5e9504ddb6eefbafa4ad3b00ad421858fafc0ed9ea4abd9cb68793f845cfc1",
110
+ "k1": "1f2b8080d4c9431b1dec780abb4eee3c4aaf7ff03f7dd126a7e005ec7af372e0",
111
+ "noteId": "0daebdd7f9355a215c511cadc96e73f586e07238c352b43b21c080713b79cbfc"
112
+ },
113
+ {
114
+ "name": "a second mnemonic, same host and index",
115
+ "mnemonic": "legal winner thank year wave sausage worth useful legal winner thank yellow",
116
+ "seedHex": "878386efb78845b3355bd15ea4d39ef97d179cb712b77d5c12b6be415fffeffe5f377ba02bf3f8544ab800b955e51fbff09828f682052a20faa6addbbddfb096",
117
+ "host": "mint.example",
118
+ "index": 0,
119
+ "cashRoot": "e58252908e0c10965d15d1e3894cbf206a2102c20eb9519e466e2e0e852075d430afaa22bb69ef2151cdffacc463e51068e854152b0b7c08fa2f93e6b8d16741",
120
+ "domainIndices": [
121
+ 3862277158,
122
+ 545631060,
123
+ 182776155,
124
+ 2195829862
125
+ ],
126
+ "hardened": [
127
+ true,
128
+ false,
129
+ false,
130
+ true
131
+ ],
132
+ "domainNode": "df3c9156afc2e416286df9bbfda49c6ab72305ba3d65ae0b7e2214caf581b111959d96bc0c075185ff7304214496a78f43b22ea511f97a7bf408454a7e3d3a85",
133
+ "k1": "d5376fa38f01b39d55be6dc0ea8f3aa6b91f37c183c5a394bb7e30b624855a78",
134
+ "noteId": "5b4ba51285373e8a8c0414e6b3607ea0a6b70968c94f2b9f73ffdfdedfba1c15"
135
+ },
136
+ {
137
+ "name": "a host carrying a port",
138
+ "mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
139
+ "seedHex": "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4",
140
+ "host": "127.0.0.1:8899",
141
+ "index": 0,
142
+ "cashRoot": "c7a2496e9b453a67c5d2a1f04936ec1259440d45454c795a99a66269e4cd3005111e1cc966fca2fe32f054f14caceab90449e536d94cf6935ea12a087e414f60",
143
+ "domainIndices": [
144
+ 2087962263,
145
+ 3073061246,
146
+ 2281736429,
147
+ 1205328740
148
+ ],
149
+ "hardened": [
150
+ false,
151
+ true,
152
+ true,
153
+ false
154
+ ],
155
+ "domainNode": "80bdd2d71f0235bd9e22d5838a4a4f346cf5415309ada559942a409a630e102ce214425b89b36d03d4835fdf6592d2619c74d37b746711b4ed85759ea1d358a6",
156
+ "k1": "d7bcc5c9e7015ca2688ed10e24db3f82163d5b59fc887e5dd346abf2426b1270",
157
+ "noteId": "1a8a732d7ec9811654183ecbb27b1887dec947a42c877a12bd93f952837a3887"
158
+ }
159
+ ]
160
+ }
@@ -6,6 +6,7 @@
6
6
  "files": [
7
7
  "signature.json",
8
8
  "derivation.json",
9
+ "cash-derivation.json",
9
10
  "bech32.json",
10
11
  "url-admission.json",
11
12
  "input-resolution.json",