fractal-pqc 0.7.2 → 0.9.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/README.md CHANGED
@@ -134,15 +134,35 @@ so the property is never silently over-claimed.
134
134
 
135
135
  ## What this is NOT
136
136
 
137
- > **ONE POLICY KEY PER HOLDER — OR BIND THE OUTPUT.** `authorizeAndSign` reads a 32-byte
138
- > `spendDigest` chosen by the requester. Round 7 of our own siege used that to have one
139
- > *legitimate* client of a custodian obtain a policy-key signature over **another client's
140
- > sighash** — which is exactly the "concentration" deployment this package describes. The
141
- > engine now **refuses any request that does not say which coin it is for**: supply
142
- > `spendContext.prevoutScriptPubKey` (enforced against the holder's own anchored commitment)
143
- > or explicitly assert `singlePolicyKeyPerHolder: true`. Silence is refused. Residual, stated
144
- > rather than hidden: the engine checks the scriptPubKey the commitment governs, it does not
145
- > recompute the BIP-341 sighash from the transaction.
137
+ > **NEVER SIGN A DIGEST YOU DID NOT COMPUTE YOURSELF.** Until 0.9.0 `authorizeAndSign` read a
138
+ > 32-byte `spendDigest` chosen by the requester, and round 7 of our own siege used that to have
139
+ > one *legitimate* client of a custodian obtain a policy-key signature over **another client's
140
+ > sighash**. The round-7 fix compared the presented scriptPubKey against one recomputed from the
141
+ > requester's fact hash — and round 8 walked through it, because **both sides of that comparison
142
+ > were the requester's**.
143
+ >
144
+ > An engine that signs a digest it was handed is not a policy engine, it is a signing **oracle**,
145
+ > and no guard around it helps: every check then compares values the caller controls. So the
146
+ > caller no longer supplies a digest. It supplies the **transaction**:
147
+ >
148
+ > ```js
149
+ > authorizeAndSign({ cert, pqSignatureHex, anchorEvidence, expectedLogId, policyKey,
150
+ > cutoffBlockHeight, blockMerkleRoots,
151
+ > spendContext: { tx, inputIndex, prevoutValues, prevoutScriptPubKeys } })
152
+ > ```
153
+ >
154
+ > `spendContext` is **mandatory** and a request carrying `spendDigest` is refused at the door.
155
+ > The engine recomputes the Taproot output key from the holder's **anchored** first-seen fact
156
+ > hash, requires the prevout at `inputIndex` to be that coin, and then derives the BIP-341
157
+ > sighash **itself**. Use `buildSpendContext()` — the same helper a custodian uses — so the
158
+ > holder signs exactly the bytes the policy key will sign.
159
+ >
160
+ > **THE TWO RESIDUALS, stated rather than hidden.** (1) The engine imposes **nothing on the
161
+ > outputs**: it proves *whose* coin is spent and *who* authorised it, never *where* the value
162
+ > goes. (2) `buildSpendContext` builds a **single-input** transaction while the engine accepts
163
+ > multi-input ones, so for those the holder cannot re-derive the digest with the shipped helper
164
+ > and would be signing a digest the custodian computed — the very mistake this section is about.
165
+ > **The holder must inspect the transaction, not merely sign the digest they were handed.**
146
166
  (honest scope — do not overstate)
147
167
 
148
168
  - **Not yet confirmed in a live mempool.** The sighash is proven consensus-correct against
package/bin/cli.mjs CHANGED
@@ -237,13 +237,31 @@ switch (cmd) {
237
237
  case "selftest": {
238
238
  const r = spawnSync(process.execPath, [join(__dirname, "..", "test", "vectors.mjs")], { stdio: "inherit" });
239
239
  if ((r.status ?? 1) !== 0) process.exit(r.status ?? 1);
240
- for (const f of ["transparency.mjs", "primacy.mjs", "anchoring.mjs", "conformance.mjs", "m2-policy.mjs", "bip341-scriptpath.mjs", "claims.mjs"]) {
241
- const t = spawnSync(process.execPath, [join(__dirname, "..", "test", f)], { stdio: "inherit" });
240
+ for (const f of ["transparency.mjs", "primacy.mjs", "anchoring.mjs", "conformance.mjs", "m2-policy.mjs", "bip341-scriptpath.mjs", "claims.mjs", "demo.mjs", "letter-claims.mjs"]) {
241
+ const t = spawnSync(process.execPath, [join(__dirname, "..", "test", f)],
242
+ { stdio: "inherit", env: { ...process.env, FRACTAL_SELFTEST_DEPTH: "1" } });
242
243
  if ((t.status ?? 1) !== 0) process.exit(t.status ?? 1);
243
244
  }
244
245
  process.exit(0);
245
246
  }
246
247
 
248
+ // Run every checkable assertion in the letter against this package. The letter cannot be
249
+ // sent if this is not green — which is the entire point of building it.
250
+ // The whole story in one run: identity -> commitment -> log -> Bitcoin anchor ->
251
+ // offline verification -> the custodian decision, and the six refusals. The enforcement
252
+ // point had no CLI verb at all, so a reviewer could read about it and never run it.
253
+ case "demo": {
254
+ const { runDemo } = await import("../src/demo.mjs");
255
+ process.exit(await runDemo());
256
+ }
257
+
258
+ case "verify-letter": {
259
+ const { verifyLetter } = await import("../src/verify-letter.mjs");
260
+ const { SHIPPED_LETTER } = await import("../src/letter-claims.mjs");
261
+ const p = rest.find((a) => !a.startsWith("--")) || SHIPPED_LETTER;
262
+ process.exit(await verifyLetter({ path: p, json: rest.includes("--json") }));
263
+ }
264
+
247
265
  // Verify a transparency bundle WITHOUT trusting the log that served it.
248
266
  // Everything is recomputed locally: the head signature, the Merkle root from the
249
267
  // audit path, and (when a previous head is present) the append-only property.
@@ -441,8 +459,16 @@ Usage:
441
459
  shipped here and proves each claim DIES when the
442
460
  code it names is broken. A claim no mutation can
443
461
  kill is vacuous, and is reported as such.
462
+ fractal-pqc demo THE WHOLE STORY, offline, in one run: identity,
463
+ dual-signed commitment, transparency log, Bitcoin
464
+ anchor, offline verification, and the custodian
465
+ policy engine authorising the rightful holder and
466
+ REFUSING six attacks — including a post-quantum
467
+ adversary holding the victim's classical key.
468
+ fractal-pqc verify-letter [file] Check every factual assertion in our letter
469
+ against this package. Exits non-zero if any fails.
444
470
  fractal-pqc verify-vector Check the official BIP-340 test vector
445
- fractal-pqc selftest Run everything: 304 real checks, no mocks
471
+ fractal-pqc selftest Run everything: 331 real checks, no mocks
446
472
 
447
473
  Docs: integrations/pqc-migration-kit/README.md`);
448
474
  process.exit(cmd ? 1 : 0);
@@ -0,0 +1,158 @@
1
+ Subject: Re: FractalAI — what enforces the PQ requirement (it's on npm; run it yourself)
2
+
3
+ Hi,
4
+
5
+ Three questions, and the third is the one that matters, so I'll start there — including a boundary I want to state before anything else.
6
+
7
+ **1) What enforces the post-quantum signature requirement**
8
+
9
+ Three layers. Two exist and are published. The third does not exist, and I won't pretend otherwise.
10
+
11
+ **Layer 1 — Binding (exists).** A holder's classical key (secp256k1 / Taproot output key) is bound to an ML-DSA-65 (FIPS-204, NIST Level 3) public key inside a domain-separated commitment.
12
+
13
+ **Layer 2 — Spend policy, fail-closed (exists — the actual enforcement point).** The rule a custodian runs is: **no ML-DSA-65 signature over the spend digest, bound to the holder's first-seen anchored commitment ⇒ no signature is released.** Every guard refuses by default; the signing call is the last statement in the function, and an unexpected input refuses rather than escaping.
14
+
15
+ **Layer 3 — Consensus (does NOT exist).** Bitcoin does not reject a spend for lacking a PQ signature, and nothing we ship changes that. I'll be blunter than the pitch requires: the only thing consensus ever checks is a classical Schnorr signature on the policy key. A quantum adversary who reaches that key spends anyway. What this construction actually buys is **concentration** — N exposed classical keys become one hash-hidden, rotatable, custodian-controlled key — plus a post-quantum-signed record of who was authorised to make that key sign, giving the custodian a cryptographic reason to refuse everyone else. That is a real reduction in attack surface and a governance mechanism. **It is not immunity, and we removed the sentence that said it was.**
16
+
17
+ One correction I owe you inside this same paragraph, because it is the finding of our seventh siege and it lands squarely on the sentence above. That sentence used to read "a record of who *should have been allowed to spend*", and until days before this email that was an overclaim: the engine received the spend digest as 32 opaque bytes and never learned **which output** it was signing for. Our own siege stood up a second, entirely legitimate client of the same custodian and had it obtain a policy-key signature over the **first client's** sighash. Every check passed. The concentration this paragraph describes — one policy key across many leaves — is precisely the deployment in which that is theft. It is fixed in `0.9.0`: the leaf commits to the holder's own anchored fact hash, the engine recomputes the output key from it, and **a request that does not say which coin it is for is refused** rather than defaulted. A footgun you step on by omission is a fail-open default with a paragraph next to it.
18
+
19
+ **2) You can run all of it, right now, without believing any of this**
20
+
21
+ Including this email. It makes eighteen factual assertions — every count, every block
22
+ height, every version, and five sentences about what the engine does. Each one is registered
23
+ with an executable check that reads the asserted value **out of this letter** and compares it
24
+ to a value measured from the package. Change a number in the letter and the check reads the
25
+ new number and fails; change the code and the measurement moves and it fails. Neither can
26
+ drift from the other.
27
+
28
+ ```
29
+ npx fractal-pqc verify-letter # this email, checked against the code, on your machine
30
+ ```
31
+
32
+ The letter you are reading ships inside the package, so you can also confirm nothing was
33
+ altered between my sending it and your reading it. **If any assertion fails, this email is
34
+ wrong — and you will know before I do.** It also prints, in the same output, the five things in here that no check covers: two judgements, one hand-counted aggregate, one historical figure that can no longer be re-measured, and every forward-looking commitment. Those are opinions, memories and promises, and we refuse to report them as verified.
35
+
36
+ I built that tool because of the disease in section 5, and I built it for this letter
37
+ specifically. It found three false numbers in my own draft before you saw it.
38
+
39
+ ```
40
+ npm i fractal-pqc@0.9.0
41
+ npx fractal-pqc demo # the whole path, offline, in 20 seconds — see below
42
+ npx fractal-pqc claims # every security claim we make, each with an attack
43
+ npx fractal-pqc claims --gaps # and what the green does NOT cover
44
+ npx fractal-pqc claims --mutate # break the code, watch the sentences die
45
+ npx fractal-pqc selftest # 331 checks, no mocks
46
+ ```
47
+
48
+ **Start with `demo`.** It is the answer to your question, executed rather than described.
49
+ In one offline run it generates a holder identity, dual-signs a migration commitment, appends
50
+ it to an RFC 6962 log, signs the head with ML-DSA-65, anchors it, verifies the anchor by
51
+ recomputing everything — and then runs the custodian policy engine. That last part is the
52
+ point, and it is why most of what you will watch are **refusals**: the engine authorises the
53
+ rightful holder once, and then refuses six times. Including this one, which is the attack the
54
+ whole design exists for:
55
+
56
+ > *a post-quantum adversary who already HOLDS the victim's classical key, produces a genuinely
57
+ > valid dual-signed rebinding, and an honest append-only log accepts it — and the engine still
58
+ > refuses, because the attacker loses on PRIMACY, not on the signature.*
59
+
60
+ The demo names the synthetic parts (there is no network call, so the Bitcoin attestation is
61
+ constructed locally and it says so on the line where it matters), and it ends by naming the
62
+ ceiling: consensus only ever checks a classical Schnorr signature, so this is not immunity.
63
+ It is also a test — `test/demo.mjs` fails the build if any of those six refusals ever starts
64
+ authorising, which is the only reason I am willing to put it in this email.
65
+
66
+ Published today. `verify-anchor` verifies a bundle offline: the tree head's ML-DSA-65 signature, the inclusion proof with the Merkle root **recomputed** rather than compared to one we hand you, an RFC 6962 consistency proof that no history was rewritten, and a Bitcoin anchor derived from the OpenTimestamps proof alone — height and Merkle root, which you then check against a header from **your own node**. We are not in that path.
67
+
68
+ A bundle ships in the package so you can run that path end to end;
69
+ `node_modules/fractal-pqc/examples/README.md` has the command with its flags filled in:
70
+
71
+ ```
72
+ npx fractal-pqc verify-anchor node_modules/fractal-pqc/examples/anchor-bundle.json \
73
+ --log-id <the id, obtained independently of the bundle> \
74
+ --cutoff <the height YOU choose> \
75
+ --block-merkle-root=<height>=<root read from YOUR OWN node>
76
+ ```
77
+
78
+ Those three flags are mandatory and **none of them may come from the bundle**. Pinning against an identity the bundle supplies proves nothing; there is no objective Q-day, so the cutoff is yours; and a `.ots` is inert data that merely *claims* a Bitcoin height until a real header confirms it — round 3 of our own siege forged the entire temporal frontier with about a hundred bytes precisely because that last one was optional. Change one character of the log id and it refuses. Lower the cutoff below the anchor and it reports the frontier as NOT established and says, in those words, *do not authorise a spend on this.*
79
+
80
+ To prove our OTS codec is right rather than merely self-consistent, the suite verifies the OpenTimestamps project's **own** example, produced by their reference client and confirmed in Bitcoin block **358391**. If we ever disagree with the reference implementation, our tests fail. Script-path (tapscript) is asserted against the **official BIP-341 wallet test vectors**: 7/7 scriptPubKey cases and 12/12 control blocks, byte for byte.
81
+
82
+ **3) On the $320k — the ask stands, and I've already absorbed the first slice of it**
83
+
84
+ You were right to push back: our application named a number without a line-item scope. Here is the scope, and one change I'd like you to notice.
85
+
86
+ **Milestone 1 is delivered, and it is not on this invoice.** The anchor registry — the exact gap your question exposed — is built, published, and runnable by you today, for $0. I'd rather show you a milestone than describe one.
87
+
88
+ What remains is, deliberately, **precisely what we cannot do alone**:
89
+
90
+ **Tranche 1 — $120,000 / 90 days** (M1 is delivered and unbilled; this is the first paid decision)
91
+
92
+ | # | What remains | Why money is required for it | Amount |
93
+ |---|---|---|---|
94
+ | ~~M1~~ | ~~Bitcoin-anchored first-seen registry~~ | **DELIVERED — `npx fractal-pqc verify-anchor`** | **$0** |
95
+ | **M2** | **Exposure-relative primacy, in a real custodian's signing flow** — the design is below, and the primitive is already shipped and enforced in `fractal-pqc@0.9.0`. What remains is a *custodian* running it against funded testnet spends | The code, the BIP-341 conformance and the gate exist. What is missing is a custodian, and custodian engineering time is what money buys | $120,000 |
96
+
97
+ **Tranche 2 — $80,000** — **independent external review**, published in full including negative findings, plus multisig PSBT and key rotation (today a lost ML-DSA secret is a permanent lockout, and the package says so). We cannot review ourselves; this is the one deliverable whose entire value comes from it not being us.
98
+ **Tranche 3 — $120,000** — public specification, wallet interoperability, long-term maintenance of the vectors. Success measured by independent adopters, not by our own claims.
99
+
100
+ You commit to a tranche only after the previous one is publicly verified by you. If a milestone doesn't verify, the engagement stops and you owe nothing further.
101
+
102
+ **3b) M2, in detail — because it is the part I think you will actually care about**
103
+
104
+ **Exposure-relative primacy.** The cutoff frontier in M1 has a weakness we state in our own README: the guarantee is "anchored before the cryptography broke", and **nobody knows when it broke**. Every verifier is guessing a date, and a private break earlier than the guess produces forged entries indistinguishable from real ones.
105
+
106
+ For one important class of holders, that guess is now unnecessary.
107
+
108
+ For a secp256k1 key whose hash is all the chain has ever shown — **an unspent P2PKH/P2WPKH output** — the kit proves a fact checkable in Bitcoin rather than an estimate: that the holder's post-quantum commitment was anchored in a Bitcoin block **strictly before** the block in which their public key first appeared **on chain**. We do not supply the exposure height and deliberately ship **no oracle** for it — the verifier supplies it from their own node, and the anchor is confirmed against the verifier's own block header.
109
+
110
+ Where that inequality holds, the proof depends on **no Q-day date**: at the moment of anchoring the chain had not yet revealed the public key, so the ability to break secp256k1 was not sufficient to have produced that commitment.
111
+
112
+ It is wired as a **gate**, not a report: `authorizeAndSign({ …, exposureHeight })` refuses and releases no signature when the commitment was anchored after exposure. Supplied means **enforced** — a parameter that can be passed and silently dropped is worse than one that does not exist.
113
+
114
+ And one structural change our eighth siege forced, which I would rather you heard from me: **the engine computes the digest it signs and refuses any request that carries one.** Until last week it accepted a 32-byte digest from the caller, which makes it a signing *oracle* — every check it performs then compares two values the caller controls. Our own siege used that to have one legitimate client of a custodian obtain a policy-key signature over another client's coin. The caller now supplies the transaction; the engine derives the BIP-341 sighash itself, over the prevout it verified belongs to that holder.
115
+
116
+ And the precise shape of it, which our seventh siege made us state properly: **this gate is a conjunction, never a substitution.** The cutoff you choose stays mandatory and is still applied afterwards, so a commitment anchored at 800,000 with exposure at 850,000 — the strongest position this scheme can give anyone — is still refused under a cutoff of 790,000. Supplying an exposure height *narrows* what authorises; it never rescues what the cutoff rejects. What is true, and is the whole point, is that the second condition depends on no Q-day estimate. The receipt now says exactly that, in those words, instead of the flatter sentence it used to print.
117
+
118
+ The claim is bounded, and we say so in the code itself:
119
+ - it assumes the holder published the key through **no other channel** — we measure on-chain appearance only, and a key leaked via an unconfirmed mempool broadcast or a shared xpub was derivable earlier than this proof suggests;
120
+ - it **does not save keys already exposed by an earlier spend**. You cannot anchor into the past. Those coins must be moved, and the engine says exactly that when it refuses.
121
+
122
+ We put this through a full adversarial round before writing this paragraph. It found two real defects in the reporting surface — an absent parameter yielding the strongest claim, and refusals that still returned `valid: true` — both now closed, both now covered by mutations that turn the ledger red if they regress. **That round is why this paragraph says "on chain" rather than "public", and why the two bounds above are in the package and not only in this email.**
123
+
124
+ **4) The four limits no design of this shape can close — and we ship them in the package**
125
+
126
+ 1. **The last signature is classical.** Covered above. No soft fork, no immunity.
127
+ 2. **The cutoff is a date nobody can verify.** The guarantee is "anchored before the cryptography broke", and nobody knows when it broke. A private break earlier than the chosen cutoff produces forged entries indistinguishable from real ones. **We refuse to print a Q-day**; the verifier supplies the height, and without one the engine refuses to authorise at all.
128
+ 3. **A timestamp proves "no later than", never "no earlier".** So primacy is relative to the log you pinned. Coverage is bounded by prior *adoption*, not by cryptography.
129
+ 4. **Pinning a log's identity does not pin its history.** Two same-size heads carry the same logId. Split view is detectable, never preventable; pass known heads and the engine refuses on contradiction.
130
+
131
+ **5) What I actually think we are, since you'll ask**
132
+
133
+ We are not the standard, and anyone claiming to be one at this stage is selling. Here is the specific thing we are, and you can falsify it in thirty seconds:
134
+
135
+ **Ours is the only package I know of that will tell you, on your machine, which of its own security claims it cannot back.** A claim is admissible only with three things: an executable proof, an executable *attack* that must fail, and a **mutation** of the code it names under which the claim must fail. A sentence no mutation can kill is reported as vacuous and the build breaks.
136
+
137
+ We built that because we needed it. Ten adversarial siege rounds against our own code — roughly 1,150 executed exploits — found the same disease every time and never once in the mathematics: an English sentence and a code path written separately, with a fully green test suite in between hiding the gap. Round 5 deleted a single line binding an anchor to the head it timestamps; the ledger printed all-green and 263 assertions passed, because no claim named that guard. Round 7 — run against the very paragraph in section 3b, in the days before this email — found the cross-client signature described in section 1, and found a frozen scope string still telling auditors that the M2 gate did not exist while the gate was refusing signatures. Round 10, run against this exact letter before it was sent: `cutoffBlockHeight`, `blockMerkleRoots` and `knownHeads` were each read straight off the request at every point they were needed, instead of being snapshotted once the way `anchorEvidence` already was — and for `knownHeads` that was not a theoretical gap. An accessor that shows a genuinely conflicting log head to the length check and an empty array to the loop that actually runs equivocation detection produced a real, verifiable ML-DSA-gated Schnorr signature in exactly the case section 4's limit #4 promises a refusal for. All three fields now get the same snapshot-once discipline. The `knownHeads` and `cutoffBlockHeight` exploits are each closed and covered by a mutation that turns the ledger red if either regresses. `blockMerkleRoots` got the identical fix on the identical reasoning, but we could not construct an attack that flips authorisation through it alone — every consumer looks the anchor height up by key, so a divergent read fails closed rather than open — and we are not shipping a mutation we cannot honestly make fail; that field's hardening is disclosed as defense-in-depth, not as the closure of a demonstrated bypass. Every mutation in the matrix reintroduces a bug that really shipped in this package, tagged with the round that caught it. **The matrix is simultaneously our test harness and the public record of our own failures.**
138
+
139
+ I am telling you about round 7 in the letter that asks you for money, before you could possibly have found it yourself, because the alternative is a commitment on the next page that would be worth nothing.
140
+
141
+ That is also why the retraction in section 1 is a *test*, not a footnote: the old immunity claim is a mutation, and it must kill the claim that replaced it. In a field that has watched a lot of projects overclaim quantum immunity, being the team that turned its own retraction into an executable assertion is the only credential I'd ask you to weigh.
142
+
143
+ **Four commitments that go with the money**
144
+
145
+ 1. **Every deliverable arrives with its own post-quantum receipt, Bitcoin-anchored.** You verify our work using the mechanism you're funding.
146
+ 2. **Public failure clause.** If anyone — including us — breaks the binding scheme during the grant, we publish it in full within 72 hours and stop invoicing until it's fixed. Our own siege has broken it ten times, the most recent one in the days before this email; that's not a hypothetical policy.
147
+ 3. **Vectors stay CC0.** 33 of them are already published. Whoever runs them owns their own verification.
148
+ 4. **Non-dilutive, open source, no exclusivity.**
149
+
150
+ **One more thing, possibly worth more than the money.** Galaxy Research publishes. I'd rather this end as a citable artefact than a vendor deliverable: publish the binding spec and the conformance vectors **jointly**, with your team as reviewers, and every red-team finding — ours included — disclosed. Whoever owns the vectors that wallets run shapes how this migration gets done.
151
+
152
+ **Where we stand, plainly.** FRACTAL AI S.A.S. (Colombia). We run a post-quantum L1 in production whose consensus signs every block with ML-DSA-65 — a single-operator chain, which we state publicly rather than calling it a network. We are pre-revenue. We are not a Bitcoin protocol team, which is exactly why the kit is validated against official BIP vectors rather than our own assumptions.
153
+
154
+ Could we do 20 minutes this week? Agenda: (1) the enforcement path line by line, including the attack that broke v1; (2) the four limits, and whether you agree they are irreducible; (3) whether this tranche structure fits how you fund.
155
+
156
+ Best,
157
+ John Edward Romo Sánchez
158
+ CEO, FRACTAL AI S.A.S. (NIT 902025722-1) · helloinvestor@fractalai.net.co
package/package.json CHANGED
@@ -1,15 +1,13 @@
1
1
  {
2
2
  "name": "fractal-pqc",
3
- "version": "0.7.2",
3
+ "version": "0.9.0",
4
4
  "description": "Runnable reference for quantum-safe migration of a Bitcoin-style key: bind secp256k1/Taproot to ML-DSA-65 (FIPS-204), derive P2TR addresses, build+sign BIP-341 key-path spends (official-vector-verified), and broadcast on testnet. Real primitives, honest scope.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "FractalAI",
8
8
  "homepage": "https://fractalai.net.co",
9
- "repository": {
10
- "type": "git",
11
- "url": "https://github.com/fractalai/fractal-ai",
12
- "directory": "integrations/pqc-migration-kit"
9
+ "bugs": {
10
+ "email": "helloinvestor@fractalai.net.co"
13
11
  },
14
12
  "engines": {
15
13
  "node": ">=18"
@@ -37,19 +35,22 @@
37
35
  "README.md",
38
36
  "bin",
39
37
  "examples",
38
+ "letter",
40
39
  "src",
41
40
  "test",
42
41
  "tools",
43
42
  "vectors"
44
43
  ],
45
44
  "scripts": {
46
- "test": "node test/vectors.mjs && node test/transparency.mjs && node test/primacy.mjs && node test/anchoring.mjs && node test/conformance.mjs && node test/m2-policy.mjs && node test/bip341-scriptpath.mjs && node test/claims.mjs",
45
+ "test": "node test/vectors.mjs && node test/transparency.mjs && node test/primacy.mjs && node test/anchoring.mjs && node test/conformance.mjs && node test/m2-policy.mjs && node test/bip341-scriptpath.mjs && node test/claims.mjs && node test/demo.mjs && node test/letter-claims.mjs",
47
46
  "selftest": "node bin/cli.mjs selftest",
48
47
  "conformance": "node test/conformance.mjs",
49
48
  "claims": "node bin/cli.mjs claims",
50
49
  "mutate": "node bin/cli.mjs claims --mutate",
51
50
  "attribution": "node bin/cli.mjs claims --attribution",
52
- "gaps": "node bin/cli.mjs claims --gaps"
51
+ "gaps": "node bin/cli.mjs claims --gaps",
52
+ "verify-letter": "node bin/cli.mjs verify-letter",
53
+ "demo": "node bin/cli.mjs demo"
53
54
  },
54
55
  "dependencies": {
55
56
  "@noble/curves": "^2.2.0",