provared 0.5.0 → 0.7.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.
@@ -91,6 +91,47 @@ follows the checking steps of the Web Authentication standard.
91
91
  section 6b. Slips should still be short-lived.
92
92
  - Who the issuer is. The slip proves which key signed, not whose key it
93
93
  is.
94
+ - Against a page that has the passkey sign a record dressed as a sign-in,
95
+ where the passkey's website name (its `rpId`) serves anything but the
96
+ signing page. A passkey signs only a challenge, and the challenge for a
97
+ slip, an approval or a cancellation is the fingerprint of the record,
98
+ which the person cannot read on the device's prompt. Any page at an
99
+ origin under the slip's `rpId` (the website's own operator, a script
100
+ running on one of its pages, or the operator of any subdomain) can hand
101
+ the device the fingerprint of a record it wrote as an ordinary sign-in
102
+ challenge: the client data of a sign-in and of a record have the same
103
+ shape, and the person sees a sign-in prompt. No device and no page need
104
+ be broken into. The checker compares the origin and the rpId with what
105
+ the slip itself says; and, where the reader names the address they
106
+ expect the person to sign at (`issuerOrigins`, `--origin`, or the box
107
+ on the checking page; format description, section 4.4), with that
108
+ address too, refusing a slip signed anywhere else
109
+ (`issuer-origin-not-expected`). What each measure does:
110
+ - A passkey made for a name that serves only the signing page closes
111
+ this for that passkey: the device lets it sign only for pages under
112
+ that name, so no other page can have it sign anything. Provared's own
113
+ signing page is served at sign.prova.red, a name that serves nothing
114
+ else.
115
+ - A reader who names the address is protected by naming alone against
116
+ every other address under the `rpId`, whatever name the passkey was
117
+ made for: the client data the passkey signs must say that address, so
118
+ a record signed at any other address under the `rpId` is refused. It
119
+ does not protect against another page served at the named address
120
+ itself.
121
+ - A page that shows the record before it asks for the signature helps
122
+ only against a page that asks openly for a record to be signed. It
123
+ does nothing against a prompt dressed as a sign-in, which shows no
124
+ record.
125
+ - Naming an address protects only the reader who names it. It does not
126
+ stop the passkey signing records at other pages, which readers who
127
+ name no address, or name another one, accept.
128
+
129
+ What stays open: a reader who names no address is protected only by
130
+ which passkey they trust; whoever serves the named address (the host of
131
+ the site, or whoever holds its domain name) can serve another page
132
+ there, and a reader's naming does not tell that page from the real one;
133
+ and a passkey made for a name that serves other pages stays exposed to
134
+ every reader who does not name the address.
94
135
 
95
136
  ## 4. The Stub and the countersignature
96
137
 
@@ -130,8 +171,11 @@ platform gave the call (`provared.event-id`), of one the agent's side
130
171
  chose and sent (`provared.trace-id`, `provared.request-id`), of part
131
172
  of the request and the answer (`provared.http-request`: the method, the
132
173
  address and the body; `provared.http-response`: the status and the
133
- body; no header is covered), or of the error the agent was given
134
- (`provared.error`).
174
+ body; no header is covered), of the error the agent was given
175
+ (`provared.error`), of the name of the tool that was called
176
+ (`provared.tool-name`), of the identifier the model gave the tool call
177
+ (`provared.tool-call-id`) or of the tool's result (`provared.tool-result`,
178
+ in the one form of RFC 8785, as the arguments are).
135
179
 
136
180
  - *What it shows.* Someone who holds the platform's own log can work the
137
181
  fingerprint out again and match the two records, without either
@@ -144,7 +188,11 @@ body; no header is covered), or of the error the agent was given
144
188
  share one fingerprint, so a match with one of them does not show that
145
189
  it was the same call. Only `provared.trace-id`, `provared.request-id`
146
190
  and `provared.event-id` are meant to pick out one call, and only they
147
- may be named more than once in a stub.
191
+ may be named more than once in a stub. `provared.tool-call-id` picks
192
+ out the model's request for the call, which the agent's software may
193
+ carry out more than once (a retry): two stubs then name one identifier.
194
+ A tool's name and a tool's result are content: every call of one tool
195
+ shares its name's fingerprint, and identical results share one.
148
196
  - *The body's bytes* are what RFC 9530 digests: the content after any
149
197
  content coding (such as gzip) and before any transfer coding. A
150
198
  platform that logs the decoded body of a message sent compressed will
@@ -158,8 +206,13 @@ body; no header is covered), or of the error the agent was given
158
206
  at all: which one a platform writes into its log differs from one
159
207
  platform to another, and is for whoever relies on it to find out. That
160
208
  the fingerprint was made from the text the name says: a checker cannot
161
- tell; only someone who holds that text can. An identifier that is
162
- short or easy to guess gives no privacy through its fingerprint.
209
+ tell; only someone who holds that text can. An identifier, a tool's
210
+ name or a result that is short or easy to guess gives no privacy
211
+ through its fingerprint: a tool's name is found by trying the
212
+ framework's or the server's tool names, and a short result (`"ok"`,
213
+ `{}`) by trying the likely values. The record holds fingerprints and
214
+ not the result itself so that the record stays small and fixed, not to
215
+ hide the result.
163
216
  - *Against A1 and A3* (the agent's own side). The binding is written by
164
217
  the agent's side, so a dishonest one can leave it out, or name a
165
218
  wrong identifier; only the match with the platform's log, made later,
@@ -172,10 +225,14 @@ body; no header is covered), or of the error the agent was given
172
225
  match: whoever writes the stub must give the address as sent.
173
226
  - *A reserved name not on the list* is refused (`bad-field`), so that a
174
227
  name that may be given a meaning later cannot be used now with another;
175
- so is one of the four once-only names named twice (two `provared.arguments`
228
+ so is one of the seven once-only names named twice (two `provared.arguments`
176
229
  fingerprints in one stub would leave unclear which was the call's). A
177
230
  book written before this rule that holds such a name no longer passes
178
- its check.
231
+ its check. The three names added after version 0.6.0 (`provared.tool-name`,
232
+ `provared.tool-call-id`, `provared.tool-result`) are refused by a
233
+ checker of version 0.5.0 or 0.6.0, which knows the first seven only,
234
+ and read as labels by one of 0.4.0 or earlier: a book that names them
235
+ checks in full only with a later checker.
179
236
 
180
237
  ## 4a. Limits, conditions, approvals and the other side's records
181
238
 
@@ -479,7 +536,8 @@ unnoticed).
479
536
  what is left still checks. This can hide the very stub that shows the agent outside its
480
537
  slip. A seal closes this for everything before it (section 6a). After
481
538
  the last seal, only a copy of a top fingerprint, with its number of
482
- entries, held by someone else would show it.
539
+ entries, or a copy of a later seal, held by someone else would show it
540
+ (section 6a, "Seals held elsewhere").
483
541
  - **Writing a different history with the agent's keys.** A1, A3 or A5
484
542
  can sign a second, different chain under the same slip. Countersigned
485
543
  stubs resist this, because the service's keys are needed. One-sided
@@ -549,8 +607,38 @@ person checking gives.
549
607
  - **The end of the book.** The last seal, with everything after it, can
550
608
  be removed, and what is left still checks. Only a copy of that seal, or
551
609
  of its top fingerprint and number of entries, held by someone else
552
- shows it. A public place where seals are kept would close this; it is
553
- not built.
610
+ shows it. A seal held elsewhere can be handed to the checker beside the
611
+ book (below), and a witness that signs for each seal (section 6f)
612
+ holds the latest one it signed for. A public place where seals are
613
+ kept, which gathers the seals of many books, is not built.
614
+ - **Seals held elsewhere.** The person, an auditor, or any copy kept out
615
+ of the recorder's reach can hand the checker, beside the book, up to 64
616
+ seal entries it holds (option `seals`, `--seal` on the command line, files on
617
+ the checking page). A seal at entry n stands at entry n of the
618
+ book, so the checker reports a book that ends before that place
619
+ (`book-cut-short`) and a book that holds another line there
620
+ (`seal-not-in-book`). This closes the end of a book cut off after a seal
621
+ someone else holds, back to any place before that seal, and a second
622
+ history shown to someone who holds a seal of the first. What it does
623
+ not close: a book cut back after the latest seal anyone holds; a book,
624
+ or a part of one, never shown to anyone who keeps a seal; and a holder
625
+ who keeps no seal at all. A seal handed over counts only where the
626
+ person named its recorder (`sealKeys`), and only where the device checked
627
+ its Ed25519 and ML-DSA-87 signatures; on a device without SLH-DSA (the
628
+ Python version, and browsers that have ML-DSA, such as Edge and Brave
629
+ 155 when tested) it counts on those two, and says so; on a device
630
+ without ML-DSA it is not used. It is compared with the book by its
631
+ content: the same content with other signatures is the same seal, and
632
+ is said, not reported against the book. It can only show a problem
633
+ with the book, never excuse one, so this widens nothing. Nothing in a
634
+ seal names its book, so a seal of another book by the same recorder
635
+ (one it kept before, when it started a new book) is reported against
636
+ the book being checked: hand over only that book's seals. A seal handed over that does not pass its own check is a
637
+ problem with the whole check, never set aside. Its time-stamps are
638
+ checked and shown, but are not used for the entries of the book: where
639
+ they differ from those beside the same seal in the book, the checker
640
+ says so. A Show does not use a seal held elsewhere, and says so: single
641
+ pages cannot show what the book holds at the place the seal states.
554
642
  - **Entries after the last time-stamp.** They are not time-stamped.
555
643
  - **A recorder who seals a false book.** A1 holding the agent's keys and
556
644
  the recorder's keys can write another history and seal it. A time-stamp
@@ -659,7 +747,17 @@ person checking gives.
659
747
  such a key, so the checker refuses the key itself. For the same reason
660
748
  it refuses an Ed25519 signature whose point R is of small order, under
661
749
  any key: the key's own holder could make one for any message, which one
662
- library accepts and another refuses. The key in a
750
+ library accepts and another refuses. It also refuses, itself, an
751
+ Ed25519 signature whose number S is not below the order L, which RFC
752
+ 8032 requires and some libraries do not check, and an ES256 passkey key
753
+ that is not a point on its curve (`bad-key`), which can check nothing
754
+ and which libraries report in different ways. A time-stamp signed with
755
+ Ed25519 must name SHA-512 as its fingerprint method, with no
756
+ parameters, as RFC 8419 requires, and may name the certificate that
757
+ signed it only once in each form (RFC 5035, sections 3 and 5): two
758
+ checkers could otherwise pick different certificates. A time-stamp is
759
+ read as strict DER throughout, every set it holds in the order DER
760
+ requires, so that two checkers cannot read one time-stamp in two ways. The key in a
663
761
  certificate must be written exactly as its standard sets out, so that
664
762
  two checkers cannot disagree about whether it can be read.
665
763
 
@@ -893,6 +991,174 @@ make a pass: it needs the keys of the agent that passes on.
893
991
  every stub under it, as outside the slip. The record is still sound.
894
992
  Only an eleventh pass in a row, which no slip can allow, is refused.
895
993
 
994
+ ## 6e. The denial
995
+
996
+ A denial (format description, section 28) is the agent's side's signed
997
+ statement that it did not take an action that was asked for, and who
998
+ said no: the check before acting, the person, or a rule of the software
999
+ around the agent.
1000
+
1001
+ **Protects.** That a denial in the book was signed by the agent the slip
1002
+ names, or by the helper its pass names, under a slip that is earlier in
1003
+ the book (`slip-missing`, `pass-missing`, `pass-mismatch`,
1004
+ `signature-invalid`). A6 cannot make one. A7 cannot use one to change
1005
+ any other answer: a denial is in no chain, adds to no total, and is never
1006
+ counted against the agent, so a flood of denials, or a denial that names
1007
+ an action far outside the slip, makes no stub read as outside it. A
1008
+ denial changed after it was signed, or under another kind of record's
1009
+ labels, is refused like any record. In this library, `act` writes a
1010
+ denial only where it did not take the action, so one call through `act`,
1011
+ `recordTools` or `record_function` leaves a stub or a denial, never both
1012
+ (the connector for an agent's tools asks for no denial until it knows
1013
+ the person will not approve). `deny` is different: it records whatever
1014
+ its caller states, and a caller can write a denial for a request that
1015
+ also has a stub; the checker shows both. The writer keeps to a set
1016
+ number of denials an hour of its own clock (600 by default), counting
1017
+ each it writes at the clock's time, so that an agent that asks again and
1018
+ again cannot fill its book quickly: at 600 an hour a book's 100,000
1019
+ entries still fill in about a week. A stub is never kept out by that
1020
+ limit.
1021
+
1022
+ **Does not protect.**
1023
+
1024
+ - **It is the agent's side's own word.** A3 can write a denial for an
1025
+ action it then took some other way, or a false reason. Only the other
1026
+ side's records, or the service's own refusal, can show otherwise.
1027
+ - **An agent can simply not write one.** A book with no denial shows
1028
+ nothing about how often the agent was stopped. A1 or A2 can also leave
1029
+ denials out of a copy of the book after the last seal, as any entry.
1030
+ - **What was asked for is in clear.** A denial names the action, the
1031
+ amount and the service. Its documents are fingerprints only, and the
1032
+ library never writes arguments into it; but a fingerprint of a short
1033
+ or guessable value can be guessed.
1034
+ - **The source is not checked.** Nothing shows that the person, or a
1035
+ rule, said no: `person` and `rule` are the agent's side's statement.
1036
+ A denial by the check before acting names the codes the check gave,
1037
+ and nothing in the record shows that the check was made.
1038
+ - **Older checkers** refuse a book that holds a denial (`unknown-entry`).
1039
+
1040
+ ## 6f. Witnesses, and a book shown two ways
1041
+
1042
+ A recorder that keeps a book (A1, A2) can show one history to one reader
1043
+ and another to another (a "split view"): each copy checks, and each is
1044
+ sealed, so a reader who holds one copy cannot see the other. A witness
1045
+ (format description, section 29) is a party with a key set of its own
1046
+ that keeps, for each recorder, person and agent, the latest seal it
1047
+ signed for, and signs a statement for a new seal only where the new seal
1048
+ extends that one: in the same book, by a consistency proof (RFC 9162,
1049
+ section 2.1.4), or as the first seal of the next book (section 21.6). The
1050
+ person and the agent are those the permission on a book's first line
1051
+ names: its passkey and the agent's key set. So every permission one
1052
+ person gives one agent under one recorder, renewals included, is one
1053
+ sequence: the independent review showed that a sequence known by the
1054
+ permission alone let a book that started with a renewal fork the
1055
+ history, and a witness now refuses it. The statement sits beside the seal on its line, or is
1056
+ handed to a checker later with a copy of the seal.
1057
+
1058
+ **Protects.** That a statement a checker counts was signed by a witness
1059
+ the person named, by the fingerprint of its key set, and speaks of that
1060
+ very seal: its `seal`, `size` and `root` must be the seal's, both its
1061
+ signatures must check, and it may not be dated more than 300 seconds
1062
+ before the seal (`witness-invalid`). A6 and A7 cannot make one, and a
1063
+ statement from a witness nobody named is shown and not counted. A
1064
+ statement that does not pass is a problem; one handed over beside the
1065
+ book is a problem with the whole check, and the seal it came with is
1066
+ used all the same. Being witnessed excuses nothing: it changes no other
1067
+ answer, and a book with a problem is never said to be witnessed.
1068
+
1069
+ A witness knows which books are one sequence only by the passkey and the
1070
+ agent of the permission on their first line and the keys of their
1071
+ recorder, and the recorder (A1, A2) chooses the first line and its keys: it can start a history with another first line, or
1072
+ seal it with new keys, and the same honest witness signs for it as
1073
+ another sequence. So a statement counts only where the person fixed
1074
+ both: the book's first line is a permission that passed its check,
1075
+ signed by a passkey the person named (`issuerKeys`), and the person
1076
+ named the recorders they expect (`sealKeys`). The independent review of
1077
+ this step showed a history that began with made-up terms, then the
1078
+ permission, signed by the same witness and read as witnessed; it now
1079
+ reads as not witnessed, and the witness itself no longer keeps a book
1080
+ whose first line is not a permission.
1081
+
1082
+ And every permission among the entries a seal covers must name the same
1083
+ passkey and agent as the first line's. The third round of the review
1084
+ showed a fork through a second agent of the same person under the same
1085
+ recorder, the gateway's own set-up: a book that started with agent Y's
1086
+ permission, then held agent X's permission and X's stubs without the
1087
+ one over its limit, was signed by the witness as the first book of the
1088
+ person and agent Y, and read as witnessed. It now reads as not
1089
+ witnessed, with a note saying why; so does one that starts with a
1090
+ second passkey's permission for X. A witness cannot refuse such a book
1091
+ itself: it sees a book's first line, not its entries. A Show counts a
1092
+ statement only where its pages hold every entry the seal covers, so it
1093
+ never counts one that the whole book would not.
1094
+
1095
+ What it narrows, where the witness keeps section 29.3 and the person
1096
+ fixed the start: a second history sealed by the same recorder and
1097
+ starting from any permission that passkey gave the same agent needs that
1098
+ witness to sign for it too, which it does not do. So two readers who name that witness, the
1099
+ same passkey and the same recorder cannot be shown two such histories
1100
+ that both carry its statements and that disagree before the earlier of
1101
+ the two seals it signed for. A new book cannot start from an earlier
1102
+ seal than the latest the witness holds, so the books of one permission
1103
+ cannot fork either. Two witnesses need two to fail. The latest seal a
1104
+ witness holds, handed over beside the book it is of as a seal held
1105
+ elsewhere (section 6a), shows that book cut back to before that seal
1106
+ (`book-cut-short`, `seal-not-in-book`). The witness program in the
1107
+ library (`openWitness`) checks a seal's three signatures, signs nothing
1108
+ on a device that cannot check SLH-DSA (so there is none in the Python
1109
+ version), ties the first line to the seal with an audit path at first
1110
+ contact and at each new book, keeps it tied with each consistency proof,
1111
+ never signs two different seals of one book at one size, never again
1112
+ signs for a book once it has signed for the next, and hands back what it
1113
+ holds with each statement, to be kept before the statement is given
1114
+ out.
1115
+
1116
+ **Does not protect.**
1117
+
1118
+ - **Other recorder keys, or another agent.** A history sealed with
1119
+ another key set, even one the person also named, or that starts from a
1120
+ permission for another agent, is another sequence to a witness, and a
1121
+ fork made that way is not caught. A person who names two recorders
1122
+ should know that a fork sealed with the second reads as witnessed. One
1123
+ recorder key set keeps one sequence for each person and agent, so books
1124
+ kept side by side for one agent need recorder keys of their own, and
1125
+ are then other sequences.
1126
+ - **What a later book holds.** A later book can repeat, or leave out,
1127
+ what an earlier book holds: each book is checked on its own.
1128
+ - **A book sealed again under another number.** A book cut short can be
1129
+ sealed again with another book number. A seal held elsewhere is then of
1130
+ another number than the book, is not compared with it, and gives no
1131
+ pass: the command-line checker says "Seals held elsewhere compared?
1132
+ NO" and does not end with code 0, and the checking page gives no green
1133
+ answer. The reader learns that their seal was not compared, not where
1134
+ the book was cut.
1135
+ - **What is never shown to a witness.** A recorder can keep a book, or
1136
+ the end of one, from every witness. Nothing in a book obliges it to be
1137
+ witnessed, and a checker that is not handed the witness's latest seal
1138
+ does not know of later seals. Where that latest seal is of a later
1139
+ book, a checker of an earlier book cannot use it to show that earlier
1140
+ book cut short: it cannot tell whether a seal of the next book is that
1141
+ book's first (section 21.6).
1142
+ - **A witness that fails.** A witness that signs for two histories, or
1143
+ that loses what it holds or is put back to an older copy of it, can
1144
+ sign a fork, and nothing in either history shows it. The library's
1145
+ witness, opened again from what it kept, signs nothing until it is
1146
+ brought up to date (`update`) and told so (`ready`); it cannot tell
1147
+ whether what it was given is the latest it gave out. Whoever can change
1148
+ what it keeps can make it forget a sequence, which it then signs for
1149
+ again from any seal: its state must be kept as safely as its keys.
1150
+ - **The entries.** A witness sees a seal's size and top fingerprint,
1151
+ not the entries: it shows that the entries existed in that form when
1152
+ it saw the seal, not that they are true.
1153
+ - **Time.** A statement's `when` is the witness's own word, and is not
1154
+ used as a time-stamp.
1155
+ - **Limits across books.** A checker counts a limit within the book it
1156
+ checks, not across the books of a permission.
1157
+ - **A device that cannot check both of a statement's signatures**
1158
+ counts nothing, and the record is not fully checked there.
1159
+ - **Older checkers** refuse a seal's line that holds `witnesses`
1160
+ (`unknown-entry`), and a seal that gives `book` (`bad-field`).
1161
+
896
1162
  ## 7. The checker and the checking page
897
1163
 
898
1164
  **Protects.** A7 cannot crash the checker, make it run a script, or make
@@ -923,7 +1189,11 @@ it show a pass for a record that does not check.
923
1189
  the checker itself put there.
924
1190
  - "Within its slip" is said only if every stub and every pass was
925
1191
  compared on this device.
926
- - Records over a fixed size, or nested too deeply, are refused.
1192
+ - Records over a fixed size, or nested too deeply, are refused. A book
1193
+ may hold 100,000 lines of 131,072 bytes, about 13 gigabytes in all;
1194
+ this library takes a book as one text, so it can check no book larger
1195
+ than one text the device can hold, and a checker should bound the size
1196
+ it accepts (format description, section 9).
927
1197
  - The content of a record must be written in one canonical form (RFC
928
1198
  8785). A record with a repeated field name, stray spaces or an unusual
929
1199
  number form is refused, so two checkers cannot read the same record in
@@ -934,11 +1204,12 @@ it show a pass for a record that does not check.
934
1204
  It asks for every header from the first block onwards, so a node does
935
1205
  not learn which blocks matter to the person.
936
1206
  - What the person hands the checking page beside a record (their own
937
- copies of cancellations, disclosures for covered fields, the blocks
938
- and fingerprints they trust) is read in the browser and sent nowhere.
939
- A file that is not JSON, or a line that is not a fingerprint, stops
940
- the check and says so: it is never set aside silently. The sample
941
- record is checked without the files handed over.
1207
+ copies of cancellations, disclosures for covered fields, the blocks,
1208
+ the fingerprints and the signing page's address they trust) is read in
1209
+ the browser and sent nowhere. A file that is not JSON, or a line that
1210
+ is not a fingerprint or a page address in its plain form, stops the
1211
+ check and says so: it is never set aside silently. The sample record
1212
+ is checked without the files handed over.
942
1213
  - The checker depends on no outside code. It uses only the cryptography
943
1214
  built into the browser or Node.js.
944
1215
  - **The checking page as one file** (`tools/make-single-page.mjs`) holds
@@ -988,7 +1259,10 @@ it show a pass for a record that does not check.
988
1259
  ## 8. What is refused, and the test that proves it
989
1260
 
990
1261
  Each row is one test in `test/refusals.test.mjs`, named by its code. A
991
- further test reads this table and fails if a code here has no test.
1262
+ further test reads this table and fails if a code here has no test. This
1263
+ table is also the format's own list of finding codes, from which a denial
1264
+ names its reasons (format description, section 28); `test/denial.test.mjs`
1265
+ fails if the two differ.
992
1266
 
993
1267
  | Code | What is refused or reported |
994
1268
  |---|---|
@@ -1001,8 +1275,8 @@ further test reads this table and fails if a code here has no test.
1001
1275
  | `payload-not-canonical` | Content that is not in the canonical form, including a repeated field name |
1002
1276
  | `payload-type-mismatch` | Content of one kind under the label of another, for example a stub presented as a countersignature |
1003
1277
  | `bad-field` | A missing, extra or wrongly typed field |
1004
- | `bad-key` | A public key that is not exactly of an accepted kind |
1005
- | `signature-invalid` | A signature that does not fit the key |
1278
+ | `bad-key` | A public key that is not exactly of an accepted kind, an Ed25519 key of small order or written in more than one way, or an ES256 key that is not a point on its curve |
1279
+ | `signature-invalid` | A signature that does not fit the key, or an Ed25519 signature whose R is a point of small order or whose S is not below the order L |
1006
1280
  | `passkey-wrong-type` | A passkey answer that was made for creating a passkey, not for signing |
1007
1281
  | `passkey-challenge-mismatch` | A passkey signature made for other content |
1008
1282
  | `passkey-origin-mismatch` | A passkey used on a website other than the one the slip names |
@@ -1011,6 +1285,7 @@ further test reads this table and fails if a code here has no test.
1011
1285
  | `passkey-user-not-verified` | A passkey signature where the device did not confirm the person |
1012
1286
  | `passkey-bad-data` | Passkey values that are cut short or malformed |
1013
1287
  | `issuer-not-expected` | A slip signed by a key other than the ones the checker was told to expect |
1288
+ | `issuer-origin-not-expected` | A slip signed at a page address other than the ones the checker was told to expect (section 3) |
1014
1289
  | `slip-missing` | A stub whose slip is not earlier in the book |
1015
1290
  | `slip-unusable` | A stub whose slip failed its own check |
1016
1291
  | `chain-broken` | A stub that is missing, moved, repeated or changed |
@@ -1041,10 +1316,13 @@ further test reads this table and fails if a code here has no test.
1041
1316
  | `seal-mismatch` | A seal that does not fit the entries before it: an entry was changed, added or removed after the seal was made. In a Show: a seal for another book, or for another length of this one |
1042
1317
  | `seal-chain-broken` | A seal that does not name the seal before it: a seal is missing, moved or changed |
1043
1318
  | `sealer-not-expected` | A seal signed with keys other than the ones the checker was told to expect |
1319
+ | `book-cut-short` | A book that ends before the place of a seal held elsewhere and handed over beside it: its end was cut off after that seal was made |
1320
+ | `seal-not-in-book` | A book that holds another line at the place of a seal held elsewhere and handed over beside it: it was changed after that seal was made, or the seal is of another book |
1321
+ | `witness-invalid` | A witness statement, beside a seal in the book or handed over with a seal held elsewhere, whose signatures do not fit the keys it gives, that speaks of another seal, or that is dated more than 300 seconds before its seal |
1044
1322
  | `dated-after-stamp` | An entry dated later than the outside time-stamp that covers it, or a seal dated later than its own time-stamp |
1045
1323
  | `stamp-bad-data` | A time-stamp that is not laid out as its standard sets out |
1046
1324
  | `stamp-wrong-data` | A time-stamp made for something other than this seal |
1047
- | `stamp-invalid` | A time-stamp whose signature does not check, or that is dated outside the time its service's certificate was in force |
1325
+ | `stamp-invalid` | A time-stamp whose signature does not check, that names the certificate that signed it twice in one form, that is signed with Ed25519 and names a fingerprint method other than SHA-512, or that is dated outside the time its service's certificate was in force |
1048
1326
  | `headers-invalid` | A chain of block headers in which a header breaks a rule that every Bitcoin node applies, that does not hold the known blocks at their places, or that ends before the last of them (tested in `test/headers.test.mjs`) |
1049
1327
  | `record-not-sound` | The check before acting: the record so far has a problem, or could not be checked on this device, so no action is allowed |
1050
1328
  | `proof-invalid` | A one-page proof that does not lead to the top fingerprint |
package/net/headers.js CHANGED
@@ -232,6 +232,7 @@ export function apart(a, b) {
232
232
  */
233
233
  export function nodeAddress(text) {
234
234
  const line = String(text).split('#')[0].trim();
235
+ // eslint-disable-next-line security/detect-unsafe-regex -- no repetition inside repetition can match the same text two ways (labels split by a full stop the labels cannot match), so matching takes time in proportion to the input.
235
236
  const m = line.match(/^\[([0-9a-fA-F:.]+)\](?::(\d{1,5}))?$/) ?? line.match(/^([A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*)\.?(?::(\d{1,5}))?$/);
236
237
  if (!m) return null;
237
238
  const host = m[1].toLowerCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "provared",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Proof of what an AI agent was allowed to do, and what it then did. A passkey-signed permission with limits a machine can check, a signed receipt for each action, and a free checker. Evidence, not a verdict.",
5
5
  "author": "Pavel Izmaylov",
6
6
  "license": "Apache-2.0 AND CC-BY-4.0",
@@ -71,7 +71,7 @@
71
71
  "node": ">=24.7"
72
72
  },
73
73
  "scripts": {
74
- "test": "node --disable-warning=ExperimentalWarning --test",
74
+ "test": "node --disable-warning=ExperimentalWarning --test \"test/**/*.test.mjs\"",
75
75
  "check": "node --disable-warning=ExperimentalWarning bin/provared-check.mjs",
76
76
  "headers": "node bin/provared-headers.mjs",
77
77
  "demo": "node --disable-warning=ExperimentalWarning demo/server.mjs"
@@ -6,7 +6,7 @@
6
6
  // request at all.
7
7
 
8
8
  import { checkBook, checkShow } from '../src/check.js';
9
- import { readBlocks, readFingerprints, renderResult, shown } from './render.js';
9
+ import { readBlocks, readFingerprints, readHanded, readOrigins, renderResult, shown } from './render.js';
10
10
 
11
11
  const status = document.getElementById('status');
12
12
  const result = document.getElementById('result');
@@ -14,9 +14,6 @@ const result = document.getElementById('result');
14
14
  // This page reads the whole file into memory. A larger book is checked
15
15
  // with the command-line checker.
16
16
  const MAX_FILE_BYTES = 200000000;
17
- // A copy of a cancellation, or a file of disclosures, is small.
18
- const MAX_HANDED_BYTES = 1000000;
19
- const MAX_COPIES = 16;
20
17
 
21
18
  // What the person already trusts: one fingerprint to a line. A line that
22
19
  // is not a fingerprint stops the check, and the person is told which box.
@@ -25,6 +22,7 @@ const BOXES = [
25
22
  ['trust-sealer', 'sealKeys', 'whoever keeps and seals the book'],
26
23
  ['trust-stamps', 'stampServices', 'time-stamp services'],
27
24
  ['trust-vouchers', 'vouchers', 'organisations whose vouching you trust'],
25
+ ['trust-witnesses', 'witnesses', 'witnesses you trust'],
28
26
  ];
29
27
 
30
28
  function trusted() {
@@ -39,37 +37,24 @@ function trusted() {
39
37
  throw new Error('A line in the box for blocks is not the fingerprint of a block. It is 64 characters: the digits 0 to 9 and the letters a to f. Correct it, or empty the box.');
40
38
  }
41
39
  if (blocks.values.length) options.blocks = blocks.values;
42
- return options;
43
- }
44
-
45
- async function jsonFrom(file, what) {
46
- if (file.size > MAX_HANDED_BYTES) throw new Error(`The file of ${what}, ${shown(file.name)}, is too large. Nothing was checked.`);
47
- try {
48
- return JSON.parse(await file.text());
49
- } catch {
50
- throw new Error(`The file of ${what}, ${shown(file.name)}, is not JSON. Nothing was checked. Choose another file, or clear these files.`);
40
+ // The addresses at which the person expects the permissions to have been
41
+ // signed: each in the one form the format allows, or the check stops.
42
+ const origins = readOrigins(document.getElementById('trust-origins').value);
43
+ if (origins.bad.length) {
44
+ throw new Error('A line in the box for the address of the signing page is not a page address in its plain form: https://, a website name in lower case, a port only if it is needed, and nothing more, such as https://sign.example.org. Correct it, or empty the box.');
51
45
  }
46
+ if (origins.values.length) options.issuerOrigins = origins.values;
47
+ return options;
52
48
  }
53
49
 
54
- // What the person was handed with the record: their own copies of
55
- // cancellations, and disclosures for covered fields. A file that cannot be
56
- // read stops the check: it is never set aside silently.
57
- async function handedOver() {
58
- const options = {};
59
- const said = [];
60
- const copies = [...document.getElementById('cancellations').files];
61
- if (copies.length > MAX_COPIES) throw new Error('At most 16 copies of cancellations can be handed over at once. Nothing was checked.');
62
- if (copies.length) {
63
- options.cancellations = [];
64
- for (const file of copies) options.cancellations.push(await jsonFrom(file, 'a cancellation'));
65
- said.push(copies.length === 1 ? 'one copy of a cancellation' : `${copies.length} copies of cancellations`);
66
- }
67
- const [disclosures] = document.getElementById('disclosures').files;
68
- if (disclosures) {
69
- options.disclosures = await jsonFrom(disclosures, 'disclosures');
70
- said.push('disclosures for covered fields');
71
- }
72
- return { options, said };
50
+ // What the person was handed with the record: the files chosen in the
51
+ // panel "What you were handed with the record" (render.js, readHanded).
52
+ function handedOver() {
53
+ return readHanded({
54
+ cancellations: [...document.getElementById('cancellations').files],
55
+ seals: [...document.getElementById('seals').files],
56
+ disclosures: document.getElementById('disclosures').files[0] ?? null,
57
+ });
73
58
  }
74
59
 
75
60
  async function check(text, name, { beside = true, trust = null } = {}) {
@@ -131,6 +116,7 @@ if (chooser.files.length > 0) checkChosen(chooser);
131
116
 
132
117
  document.getElementById('clear-beside').addEventListener('click', () => {
133
118
  document.getElementById('cancellations').value = '';
119
+ document.getElementById('seals').value = '';
134
120
  document.getElementById('disclosures').value = '';
135
121
  status.textContent = 'The files you were handed with a record are cleared.';
136
122
  });
package/page/index.html CHANGED
@@ -20,12 +20,16 @@
20
20
  <p class="quiet">A check says more when you tell it which keys and services you expect. Put one fingerprint on each line. Use fingerprints you obtained yourself, from the people concerned.</p>
21
21
  <label for="trust-issuer">The person's passkey (its thumbprint)</label>
22
22
  <textarea id="trust-issuer" rows="2" spellcheck="false"></textarea>
23
+ <label for="trust-origins">The address of the page at which the person signs permissions (such as https://sign.example.org; a permission signed at any other address is refused)</label>
24
+ <textarea id="trust-origins" rows="2" spellcheck="false"></textarea>
23
25
  <label for="trust-sealer">Whoever keeps and seals the book (the fingerprint of its key set)</label>
24
26
  <textarea id="trust-sealer" rows="2" spellcheck="false"></textarea>
25
27
  <label for="trust-stamps">Time-stamp services (the fingerprint of each service's certificate)</label>
26
28
  <textarea id="trust-stamps" rows="2" spellcheck="false"></textarea>
27
29
  <label for="trust-vouchers">Organisations whose vouching for names you trust (the fingerprint of each one's key set)</label>
28
30
  <textarea id="trust-vouchers" rows="2" spellcheck="false"></textarea>
31
+ <label for="trust-witnesses">Witnesses you trust to have seen the seals (the fingerprint of each one's key set)</label>
32
+ <textarea id="trust-witnesses" rows="2" spellcheck="false"></textarea>
29
33
  <label for="trust-blocks">Blocks of a public blockchain that you trust to be part of the chain (the fingerprint of each block: 64 characters, the digits 0 to 9 and the letters a to f)</label>
30
34
  <textarea id="trust-blocks" rows="2" spellcheck="false"></textarea>
31
35
  </details>
@@ -34,6 +38,8 @@
34
38
  <p class="quiet">Choose these files first, then choose the record file. They stay chosen, and are used with every record you check, until you clear them. The sample record is checked without them.</p>
35
39
  <label for="cancellations">The person's own copies of cancellations: one file for each, up to 16. A copy counts even where the book leaves the cancellation out. A file may also hold what the agent's side signed when it was handed the cancellation.</label>
36
40
  <input type="file" id="cancellations" accept=".json,.txt" multiple>
41
+ <label for="seals">Seals of the book held elsewhere, by you, an auditor or a copy kept apart: one file for each, up to 64. The book must hold each at the place it states; one that ends before it was cut short. A seal counts only where you named its recorder above. A file may also hold statements that witnesses signed for the seal.</label>
42
+ <input type="file" id="seals" accept=".json,.txt" multiple>
37
43
  <label for="disclosures">Disclosures for covered fields of a slip: one file</label>
38
44
  <input type="file" id="disclosures" accept=".json,.txt">
39
45
  <p><button type="button" class="second" id="clear-beside">Clear these files</button></p>