kairos-chain 3.45.0 → 3.47.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +78 -0
- data/lib/kairos_mcp/version.rb +1 -1
- data/templates/skillsets/synoptis/bin/rpr_verify.rb +171 -0
- data/templates/skillsets/synoptis/bin/sdp_verify.rb +174 -0
- data/templates/skillsets/synoptis/lib/synoptis/anchoring/conventions/rpr-1.md +141 -0
- data/templates/skillsets/synoptis/lib/synoptis/anchoring/conventions/sdp-1.md +182 -0
- data/templates/skillsets/synoptis/lib/synoptis/anchoring/reproduction.rb +316 -0
- data/templates/skillsets/synoptis/lib/synoptis/anchoring/selective_disclosure.rb +540 -0
- data/templates/skillsets/synoptis/test/test_rpr_reproduction.rb +238 -0
- data/templates/skillsets/synoptis/test/test_sdp_disclosure.rb +434 -0
- metadata +9 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e43541279a3d104b85372574c94f6fdf2d96188fb7de83d5a4d84c3f470634d7
|
|
4
|
+
data.tar.gz: 21b58d11561dd6d4177a7aa3d3832bdfe4fbde80ff2da2af198662d655a6fc81
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 41bc6c95594567c2e80ed006ea2bd21e7926361bf3cfb14fdf5a6b0a4aac17b9dca3f65faf17be1a4e86b16ce3fb9285af121d747fcdeac66eca58857d4af0bd
|
|
7
|
+
data.tar.gz: 77a5ac53384830598e90b408b4c737eb8549c219dfdeb47c38feb5939c46829c2b53dc9744da1a9914033521ff1fdf126f8d941989fe62857316863a75790e5f
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,84 @@ All notable changes to the `kairos-chain` gem will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
This project follows [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [3.47.0] - 2026-07-22
|
|
8
|
+
|
|
9
|
+
### Synoptis — selective disclosure sdp-1 (AUD-L4 slice 1)
|
|
10
|
+
|
|
11
|
+
Implements AUD-L4 design v0.3 (SDP-1..5, FROZEN, review-converged) slice 1
|
|
12
|
+
in the synoptis SkillSet. All additive: sdp-1 builds on khab-1/map-1/rpr-1
|
|
13
|
+
exactly as each prior slice built on its predecessors; no pre-existing file
|
|
14
|
+
changed.
|
|
15
|
+
|
|
16
|
+
- `conventions/sdp-1.md`: content-addressed selective-disclosure convention —
|
|
17
|
+
hash-based salted field-level disclosure (content-blinding: field values
|
|
18
|
+
hidden, record digest / field names / count public; the ZK membership
|
|
19
|
+
family is disclosed as out of scope for a later convention). Field
|
|
20
|
+
commitments (one salted digest per field, total coverage, `sdp-1/field`
|
|
21
|
+
domain separation), convention-determined disclosure profile (closed
|
|
22
|
+
schema; predicate + opened set + currency, `format` always opened),
|
|
23
|
+
presentation (closed schema per shape), three predicates
|
|
24
|
+
(typed-existence / claimed-verdict / conforming-verdict with foreignness,
|
|
25
|
+
anterior-tolerance assessment, and sibling-aware tolerance-target
|
|
26
|
+
coherence inside the checked predicate), extent-bounded currency scan
|
|
27
|
+
(issuer rule, residues disclosed unconditionally), computational
|
|
28
|
+
hiding/soundness base stated (no trusted setup).
|
|
29
|
+
- `selective_disclosure.rb`: build/parse/verify for field commitments,
|
|
30
|
+
profile, presentation (refuse-not-coerce, canonical-serialization
|
|
31
|
+
equality everywhere); signature verification from the record DIGEST alone
|
|
32
|
+
via the unchanged map-1 §1.1 attestation signature — blinded verdicts
|
|
33
|
+
without record content; conforming-verdict refuses (never degrades) when
|
|
34
|
+
operator credential or rpr-1 §2.1 assessment material is missing;
|
|
35
|
+
currency scan with unestablished-vs-unretracted discipline.
|
|
36
|
+
- `bin/sdp_verify.rb`: offline verifier (commit / binding / presentation /
|
|
37
|
+
profile / currency / convention), exit-code discipline (0/1/2), strict
|
|
38
|
+
intake (delete_suffix, canonical numerals).
|
|
39
|
+
- `test/test_sdp_disclosure.rb`: 61 design-constraint assertions
|
|
40
|
+
(SDP-1..5), including crafted-record shape refusal, verdict-vocabulary
|
|
41
|
+
closure, sibling-target coherence (rpr-1 §2.1 pooling agreement),
|
|
42
|
+
hiding of withheld values across artifacts and error paths, and
|
|
43
|
+
khab/map/rpr non-interference (37/75/76/88 regression green).
|
|
44
|
+
|
|
45
|
+
Implementation review: 3 rounds, R3 4/6 APPROVE + executable-probe
|
|
46
|
+
personas 2/2, zero remaining P0/P1 (dispositions in docs/drafts/).
|
|
47
|
+
|
|
48
|
+
## [3.46.0] - 2026-07-22
|
|
49
|
+
|
|
50
|
+
### Synoptis — reproduction endorsement rpr-1 (AUD-L3 slice 1)
|
|
51
|
+
|
|
52
|
+
Implements AUD-L3 design v0.4 (RPR-1..5, review-converged) slice 1 in the
|
|
53
|
+
synoptis SkillSet. All additive: rpr-1 builds on map-1/khab-1 exactly as
|
|
54
|
+
map-1 built on khab-1; no pre-existing file changed.
|
|
55
|
+
|
|
56
|
+
- `conventions/rpr-1.md`: content-addressed reproduction-endorsement
|
|
57
|
+
convention — re-execution target (inputs/environment/pipeline/output
|
|
58
|
+
digests; the first three form the committed computation identification,
|
|
59
|
+
output excluded), target-bound result-free tolerance declaration
|
|
60
|
+
(bit-identity at rpr-1), reproduction endorsement carrying the adjudicated
|
|
61
|
+
verdict (reproduced / not-reproduced) and its adjudication mode
|
|
62
|
+
(hand / procedure), declaration-set assessment (order-independent, sibling
|
|
63
|
+
targets pooled by computation identification, residue disclosed), map-1 §3
|
|
64
|
+
retraction reused unchanged.
|
|
65
|
+
- `reproduction.rb`: build/parse/digest for target, tolerance, endorsement
|
|
66
|
+
(closed schemas, canonical-serialization equality, refuse-not-coerce);
|
|
67
|
+
endorsement signing/verification via the unchanged map-1 §1.1 attestation
|
|
68
|
+
signature (ChainCredential); foreignness conformance check (RPR-4);
|
|
69
|
+
declaration-set assessment reporting invoked commitment (earliest
|
|
70
|
+
anterior), posterior commitments, digest-tiebroken rank, distinct-digest
|
|
71
|
+
multiplicity, and unresolved-equivalence residue.
|
|
72
|
+
- `bin/rpr_verify.rb`: offline auditor verifier (target / tolerance /
|
|
73
|
+
endorsement / assess / convention). Strict intake (at most one trailing
|
|
74
|
+
LF via delete_suffix — CRLF refused), canonical base-10 numerals only,
|
|
75
|
+
exit-code discipline (0 verified/report, 1 rejected, 2 unresolvable),
|
|
76
|
+
RPR-1/RPR-4 condition disclosure in output.
|
|
77
|
+
- `test/test_rpr_reproduction.rb`: 37 design-constraint assertions
|
|
78
|
+
(RPR-1..5), including presentation-order independence of the assessment
|
|
79
|
+
and rank, CRLF/padding refusal, and map-1 non-interference (75/75
|
|
80
|
+
regression green).
|
|
81
|
+
|
|
82
|
+
Implementation review: 3 rounds, R3 5/5 APPROVE, zero remaining P0/P1
|
|
83
|
+
(executable-probe personas; dispositions in docs/drafts/).
|
|
84
|
+
|
|
7
85
|
## [3.45.0] - 2026-07-21
|
|
8
86
|
|
|
9
87
|
### Synoptis — mutual anchoring protocol MAP-1..4 (map-1, AUD-L2)
|
data/lib/kairos_mcp/version.rb
CHANGED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# rpr_verify — offline auditor verifier for rpr-1 reproduction-endorsement
|
|
5
|
+
# artifacts (RPR-1..5, aud_l3_reproducibility_design v0.4).
|
|
6
|
+
#
|
|
7
|
+
# Verifies with EXACTLY the disclosed trust base: the artifacts named on the
|
|
8
|
+
# command line plus the shipped rpr-1 and map-1 convention definitions. No
|
|
9
|
+
# registry, no network, no operator cooperation. (Authentic chain views are
|
|
10
|
+
# the caller's inputs; their integrity is the khab-1/map-1 surfaces' business.)
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# rpr_verify.rb target <target.json>
|
|
14
|
+
# rpr_verify.rb tolerance <tolerance.json> [target.json]
|
|
15
|
+
# with target.json: additionally checks the binding matches that target.
|
|
16
|
+
# rpr_verify.rb endorsement <endorsement.json> <endorser_credential.json> <signature-hex> [operator_credential.json]
|
|
17
|
+
# with operator credential: additionally checks foreignness (RPR-4).
|
|
18
|
+
# rpr_verify.rb assess <targets.json> <declarations.json> <invoked_tolerance_sha256> <endorsement_position>
|
|
19
|
+
# targets.json = JSON array of target record STRINGS
|
|
20
|
+
# declarations.json = JSON array of {"tolerance": "<record string>", "position": <int>}
|
|
21
|
+
# rpr_verify.rb convention
|
|
22
|
+
# prints the shipped rpr-1 convention digest.
|
|
23
|
+
#
|
|
24
|
+
# Exit status: 0 = VERIFIED/REPORT, 1 = REJECTED, 2 = usage / unresolvable.
|
|
25
|
+
|
|
26
|
+
require 'json'
|
|
27
|
+
require 'digest'
|
|
28
|
+
require_relative '../lib/synoptis/anchoring/entry'
|
|
29
|
+
require_relative '../lib/synoptis/anchoring/chain_credential'
|
|
30
|
+
require_relative '../lib/synoptis/anchoring/reproduction'
|
|
31
|
+
|
|
32
|
+
CRED = Synoptis::Anchoring::ChainCredential
|
|
33
|
+
REPRO = Synoptis::Anchoring::Reproduction
|
|
34
|
+
|
|
35
|
+
def die(msg)
|
|
36
|
+
warn "rpr_verify: #{msg}"
|
|
37
|
+
exit 2
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def reject(msg)
|
|
41
|
+
puts "REJECTED: #{msg}"
|
|
42
|
+
exit 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Read a record artifact: at most one trailing newline is tolerated (file
|
|
46
|
+
# convenience); any other surrounding bytes stay in the string and fail the
|
|
47
|
+
# canonical check downstream (refuse-not-coerce — a padded artifact is not
|
|
48
|
+
# the canonical record).
|
|
49
|
+
def read_file(path)
|
|
50
|
+
# delete_suffix, not chomp: chomp("\n") also swallows "\r\n" and a bare
|
|
51
|
+
# trailing "\r", which would coerce CRLF artifacts into canonical form.
|
|
52
|
+
File.read(path).delete_suffix("\n")
|
|
53
|
+
rescue SystemCallError => e
|
|
54
|
+
die "cannot read #{path}: #{e.message}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def load_json(path)
|
|
58
|
+
JSON.parse(File.read(path))
|
|
59
|
+
rescue JSON::ParserError, SystemCallError => e
|
|
60
|
+
die "cannot read #{path}: #{e.message}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def load_json_hash(path, label)
|
|
64
|
+
parsed = load_json(path)
|
|
65
|
+
die "#{path}: #{label} must be a JSON object, got #{parsed.class}" unless parsed.is_a?(Hash)
|
|
66
|
+
parsed
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Canonical base-10 numerals only: leading zeros are refused, not
|
|
70
|
+
# reinterpreted (a non-canonical numeral is a malformed input, exit 2).
|
|
71
|
+
def strict_int(value, label)
|
|
72
|
+
die "#{label} must be a canonical base-10 integer, got #{value.inspect}" unless value.is_a?(String) && value.match?(/\A(0|[1-9]\d*)\z/)
|
|
73
|
+
Integer(value, 10)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
HEX64 = /\A[a-f0-9]{64}\z/
|
|
77
|
+
|
|
78
|
+
mode = ARGV.shift
|
|
79
|
+
|
|
80
|
+
case mode
|
|
81
|
+
when 'target'
|
|
82
|
+
die 'usage: rpr_verify.rb target <target.json>' unless ARGV.size == 1
|
|
83
|
+
record = read_file(ARGV[0])
|
|
84
|
+
begin
|
|
85
|
+
REPRO.parse_target!(record)
|
|
86
|
+
rescue REPRO::ReproductionError => e
|
|
87
|
+
reject e.message
|
|
88
|
+
end
|
|
89
|
+
puts "VERIFIED: target digest #{REPRO.target_digest(record)}"
|
|
90
|
+
puts " computation identification: #{REPRO.computation_id(record).split('|').map { |h| h[0, 12] }.join('… | ')}… (output excluded, rpr-1 §1)"
|
|
91
|
+
puts 'NOTE: binding fixes referents, never validates them (RPR-2, MPR-4).'
|
|
92
|
+
|
|
93
|
+
when 'tolerance'
|
|
94
|
+
die 'usage: rpr_verify.rb tolerance <tolerance.json> [target.json]' unless (1..2).cover?(ARGV.size)
|
|
95
|
+
record = read_file(ARGV[0])
|
|
96
|
+
tol = begin
|
|
97
|
+
REPRO.parse_tolerance!(record)
|
|
98
|
+
rescue REPRO::ReproductionError => e
|
|
99
|
+
reject e.message
|
|
100
|
+
end
|
|
101
|
+
if ARGV[1]
|
|
102
|
+
target = read_file(ARGV[1])
|
|
103
|
+
begin
|
|
104
|
+
digest = REPRO.target_digest(target)
|
|
105
|
+
rescue REPRO::ReproductionError => e
|
|
106
|
+
reject e.message
|
|
107
|
+
end
|
|
108
|
+
reject "tolerance binds target #{tol['target_sha256'][0, 12]}…, not the supplied target #{digest[0, 12]}…" unless tol['target_sha256'] == digest
|
|
109
|
+
end
|
|
110
|
+
puts "VERIFIED: tolerance digest #{Digest::SHA256.hexdigest(record)} (kind #{tol['kind']}, target #{tol['target_sha256'][0, 12]}…)"
|
|
111
|
+
puts 'NOTE: anteriority to any endorsement is decided by committed order (MPR-8), not by this tool.'
|
|
112
|
+
|
|
113
|
+
when 'endorsement'
|
|
114
|
+
die 'usage: rpr_verify.rb endorsement <endorsement.json> <endorser_credential.json> <signature-hex> [operator_credential.json]' unless (3..4).cover?(ARGV.size)
|
|
115
|
+
record = read_file(ARGV[0])
|
|
116
|
+
cred = load_json_hash(ARGV[1], 'endorser credential')
|
|
117
|
+
ok = begin
|
|
118
|
+
REPRO.verify_endorsement(record, cred, ARGV[2])
|
|
119
|
+
rescue REPRO::ReproductionError, CRED::CredentialError => e
|
|
120
|
+
reject e.message
|
|
121
|
+
end
|
|
122
|
+
reject 'endorsement signature does not verify under the endorser credential' unless ok
|
|
123
|
+
e = REPRO.parse_endorsement!(record)
|
|
124
|
+
if ARGV[3]
|
|
125
|
+
operator = load_json_hash(ARGV[3], 'operator credential')
|
|
126
|
+
begin
|
|
127
|
+
reject 'endorser credential equals operator credential — same-party, not a conforming rpr-1 endorsement (RPR-4)' unless REPRO.foreign?(cred, operator)
|
|
128
|
+
rescue CRED::CredentialError => err
|
|
129
|
+
# An unresolvable operator credential is a caller-input problem, not a
|
|
130
|
+
# verdict about the endorsement (exit-code discipline: 2, not 1).
|
|
131
|
+
die "operator credential unresolvable: #{err.message}"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
puts "VERIFIED: #{e['verdict']} endorsement of target #{e['target_sha256'][0, 12]}… under tolerance #{e['tolerance_sha256'][0, 12]}…"
|
|
135
|
+
puts " adjudication mode: #{e['adjudication_mode']}#{e['procedure_sha256'] ? " (procedure #{e['procedure_sha256'][0, 12]}…)" : ''}"
|
|
136
|
+
puts 'CONDITIONS (travel with every stronger reading, rpr-1 §3):'
|
|
137
|
+
puts ' - the verdict asserts reproduction or its failure, never correctness (RPR-1, MPR-6)'
|
|
138
|
+
puts ' - evidence of a committed claim, never proof a re-execution happened (RPR-4)'
|
|
139
|
+
puts ' - distinctness is not independence; a colluding pair can fabricate (RPR-4)'
|
|
140
|
+
puts ' - the named mode is a declaration, not a proof (map-1 §3 non-self-certifying)'
|
|
141
|
+
if ARGV[3]
|
|
142
|
+
puts ' - foreignness checked against the SUPPLIED operator credential only'
|
|
143
|
+
else
|
|
144
|
+
puts ' - foreignness (RPR-4 conformance condition) NOT assessed — no operator credential supplied'
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
when 'assess'
|
|
148
|
+
die 'usage: rpr_verify.rb assess <targets.json> <declarations.json> <invoked_tolerance_sha256> <endorsement_position>' unless ARGV.size == 4
|
|
149
|
+
targets = load_json(ARGV[0])
|
|
150
|
+
die "#{ARGV[0]}: expected a JSON array of target record strings" unless targets.is_a?(Array) && targets.all? { |t| t.is_a?(String) }
|
|
151
|
+
declarations = load_json(ARGV[1])
|
|
152
|
+
die "#{ARGV[1]}: expected a JSON array of {tolerance, position} objects" unless declarations.is_a?(Array)
|
|
153
|
+
invoked = ARGV[2]
|
|
154
|
+
die 'invoked_tolerance_sha256 must be 64-char lowercase hex' unless invoked.match?(HEX64)
|
|
155
|
+
position = strict_int(ARGV[3], 'endorsement_position')
|
|
156
|
+
begin
|
|
157
|
+
report = REPRO.assess_declarations(targets: targets, declarations: declarations,
|
|
158
|
+
invoked_tolerance_sha256: invoked, endorsement_position: position)
|
|
159
|
+
rescue REPRO::ReproductionError => e
|
|
160
|
+
reject e.message
|
|
161
|
+
end
|
|
162
|
+
puts JSON.pretty_generate(report)
|
|
163
|
+
puts 'NOTE: the assessment reports, the reader prices (RPR-3: exposure, not prevention).'
|
|
164
|
+
puts 'NOTE: a produced report always exits 0; the conformance bit is invoked_conforming in the report.'
|
|
165
|
+
|
|
166
|
+
when 'convention'
|
|
167
|
+
puts "rpr-1 convention sha256: #{REPRO.convention_sha256}"
|
|
168
|
+
|
|
169
|
+
else
|
|
170
|
+
die 'usage: rpr_verify.rb target|tolerance|endorsement|assess|convention ... (see header)'
|
|
171
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# sdp_verify — offline verifier for sdp-1 selective-disclosure artifacts
|
|
5
|
+
# (SDP-1..5, aud_l4_selective_disclosure_design v0.3).
|
|
6
|
+
#
|
|
7
|
+
# Verifies with EXACTLY the disclosed trust base: the artifacts named on the
|
|
8
|
+
# command line plus the shipped sdp-1, rpr-1, and map-1 convention
|
|
9
|
+
# definitions. No registry, no network, no producer cooperation. (Authentic
|
|
10
|
+
# chain/log views are the caller's inputs; their integrity is the
|
|
11
|
+
# khab-1/map-1 surfaces' business.)
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# sdp_verify.rb commit <record.json> [salts.json]
|
|
15
|
+
# builds the sdp-1 field-commitments auxiliary for a canonical record;
|
|
16
|
+
# prints the auxiliary record and (fresh or supplied) salts as JSON.
|
|
17
|
+
# sdp_verify.rb binding <aux.json> <record.json> <salts.json>
|
|
18
|
+
# checks the SDP-2 binding: every field digest and the record digest
|
|
19
|
+
# recompute from record + salts.
|
|
20
|
+
# sdp_verify.rb presentation <presentation.json> [operator_credential.json] [assessment.json]
|
|
21
|
+
# verifies a presentation. conforming-verdict REQUIRES both extras;
|
|
22
|
+
# assessment.json = {"targets":[...record strings...],
|
|
23
|
+
# "declarations":[{"tolerance":"...","position":N}...],
|
|
24
|
+
# "endorsement_position":N}.
|
|
25
|
+
# sdp_verify.rb profile <presentation.json>
|
|
26
|
+
# prints the disclosure profile and what it does/does not show.
|
|
27
|
+
# sdp_verify.rb currency <entries.json> <carrier_entry_hash> <extent>
|
|
28
|
+
# runs the sdp-1 §5 retraction scan over a supplied entry view.
|
|
29
|
+
# sdp_verify.rb convention
|
|
30
|
+
# prints the shipped sdp-1 convention digest.
|
|
31
|
+
#
|
|
32
|
+
# Exit status: 0 = VERIFIED/REPORT, 1 = REJECTED, 2 = usage / unresolvable.
|
|
33
|
+
|
|
34
|
+
require 'json'
|
|
35
|
+
require 'digest'
|
|
36
|
+
require_relative '../lib/synoptis/anchoring/entry'
|
|
37
|
+
require_relative '../lib/synoptis/anchoring/chain_credential'
|
|
38
|
+
require_relative '../lib/synoptis/anchoring/reproduction'
|
|
39
|
+
require_relative '../lib/synoptis/anchoring/selective_disclosure'
|
|
40
|
+
|
|
41
|
+
SD = Synoptis::Anchoring::SelectiveDisclosure
|
|
42
|
+
CRED = Synoptis::Anchoring::ChainCredential
|
|
43
|
+
|
|
44
|
+
def die(msg)
|
|
45
|
+
warn "sdp_verify: #{msg}"
|
|
46
|
+
exit 2
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def reject(msg)
|
|
50
|
+
puts "REJECTED: #{msg}"
|
|
51
|
+
exit 1
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Read a record artifact: at most one trailing newline is tolerated (file
|
|
55
|
+
# convenience); any other surrounding bytes stay in the string and fail the
|
|
56
|
+
# canonical check downstream (refuse-not-coerce). delete_suffix, not chomp:
|
|
57
|
+
# chomp also swallows CRLF, which would coerce a non-canonical artifact.
|
|
58
|
+
def read_file(path)
|
|
59
|
+
File.read(path).delete_suffix("\n")
|
|
60
|
+
rescue SystemCallError => e
|
|
61
|
+
die "cannot read #{path}: #{e.message}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def load_json(path, label)
|
|
65
|
+
parsed = JSON.parse(File.read(path))
|
|
66
|
+
die "#{path}: #{label} must be a JSON object or array, got #{parsed.class}" unless parsed.is_a?(Hash) || parsed.is_a?(Array)
|
|
67
|
+
parsed
|
|
68
|
+
rescue JSON::ParserError, SystemCallError => e
|
|
69
|
+
die "cannot read #{path}: #{e.message}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def load_json_hash(path, label)
|
|
73
|
+
parsed = load_json(path, label)
|
|
74
|
+
die "#{path}: #{label} must be a JSON object, got #{parsed.class}" unless parsed.is_a?(Hash)
|
|
75
|
+
parsed
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Canonical base-10 numerals only: leading zeros are refused, not
|
|
79
|
+
# reinterpreted (a non-canonical numeral is a malformed input, exit 2).
|
|
80
|
+
def strict_int(value, label)
|
|
81
|
+
die "#{label} must be a canonical base-10 integer, got #{value.inspect}" unless value.is_a?(String) && value.match?(/\A(0|[1-9]\d*)\z/)
|
|
82
|
+
Integer(value, 10)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
HEX64 = /\A[a-f0-9]{64}\z/
|
|
86
|
+
|
|
87
|
+
mode = ARGV.shift
|
|
88
|
+
|
|
89
|
+
case mode
|
|
90
|
+
when 'commit'
|
|
91
|
+
die 'usage: sdp_verify.rb commit <record.json> [salts.json]' unless (1..2).cover?(ARGV.size)
|
|
92
|
+
record = read_file(ARGV[0])
|
|
93
|
+
salts = ARGV[1] ? load_json_hash(ARGV[1], 'salts') : nil
|
|
94
|
+
built = begin
|
|
95
|
+
SD.build_field_commitments(record, salts: salts)
|
|
96
|
+
rescue SD::DisclosureError => e
|
|
97
|
+
reject e.message
|
|
98
|
+
end
|
|
99
|
+
puts JSON.pretty_generate(built)
|
|
100
|
+
warn 'NOTE: keep the salts private; each opened salt discloses exactly its field (sdp-1 §6).'
|
|
101
|
+
|
|
102
|
+
when 'binding'
|
|
103
|
+
die 'usage: sdp_verify.rb binding <aux.json> <record.json> <salts.json>' unless ARGV.size == 3
|
|
104
|
+
aux = read_file(ARGV[0])
|
|
105
|
+
record = read_file(ARGV[1])
|
|
106
|
+
salts = load_json_hash(ARGV[2], 'salts')
|
|
107
|
+
ok = begin
|
|
108
|
+
SD.verify_field_commitments(aux, record, salts)
|
|
109
|
+
rescue SD::DisclosureError => e
|
|
110
|
+
reject e.message
|
|
111
|
+
end
|
|
112
|
+
reject 'auxiliary is NOT checkably bound to the record (SDP-2: a bound-by-assertion auxiliary is a re-authoring)' unless ok
|
|
113
|
+
puts "VERIFIED: auxiliary is checkably bound to record #{Digest::SHA256.hexdigest(record)[0, 12]}… (total coverage, SDP-2)"
|
|
114
|
+
|
|
115
|
+
when 'presentation'
|
|
116
|
+
die 'usage: sdp_verify.rb presentation <presentation.json> [operator_credential.json] [assessment.json]' unless (1..3).cover?(ARGV.size)
|
|
117
|
+
pres = read_file(ARGV[0])
|
|
118
|
+
operator = ARGV[1] ? load_json_hash(ARGV[1], 'operator credential') : nil
|
|
119
|
+
assessment = ARGV[2] ? load_json_hash(ARGV[2], 'assessment material') : nil
|
|
120
|
+
report = begin
|
|
121
|
+
SD.verify_presentation(pres, operator_credential: operator, assessment: assessment)
|
|
122
|
+
rescue SD::DisclosureError, CRED::CredentialError => e
|
|
123
|
+
reject e.message
|
|
124
|
+
end
|
|
125
|
+
unless report[:valid]
|
|
126
|
+
puts "REJECTED: presentation does not verify (predicate #{report[:predicate]})"
|
|
127
|
+
report[:failures].each { |f| puts " - #{f}" }
|
|
128
|
+
exit 1
|
|
129
|
+
end
|
|
130
|
+
puts "VERIFIED: #{report[:predicate]} over committed record #{report[:record_sha256][0, 12]}…"
|
|
131
|
+
report[:opened].each { |n, v| puts " opened #{n} = #{v.inspect}" }
|
|
132
|
+
report[:notes].each { |n| puts " NOTE: #{n}" }
|
|
133
|
+
puts 'NOTE: soundness = sha256 collision resistance + Ed25519, no setup; hiding is computational, content-only (SDP-5, sdp-1 §6).'
|
|
134
|
+
|
|
135
|
+
when 'profile'
|
|
136
|
+
die 'usage: sdp_verify.rb profile <presentation.json>' unless ARGV.size == 1
|
|
137
|
+
pres = read_file(ARGV[0])
|
|
138
|
+
p = begin
|
|
139
|
+
SD.parse_presentation!(pres)
|
|
140
|
+
rescue SD::DisclosureError => e
|
|
141
|
+
reject e.message
|
|
142
|
+
end
|
|
143
|
+
profile = p['profile']
|
|
144
|
+
puts "profile (meaning fixed by sdp-1 §2, never producer gloss — SDP-4):"
|
|
145
|
+
puts " predicate: #{profile['predicate']}"
|
|
146
|
+
puts " opened: #{profile['opened'].join(', ')}"
|
|
147
|
+
puts " withheld: every other field of the referenced record (values only; names and count are public)"
|
|
148
|
+
puts " currency: #{profile['currency']}"
|
|
149
|
+
puts ' shows: existence and the opened values of ONE committed record'
|
|
150
|
+
puts ' never: non-existence, uniqueness, sibling records, contrary verdicts, producer selection (SDP-3)'
|
|
151
|
+
|
|
152
|
+
when 'currency'
|
|
153
|
+
die 'usage: sdp_verify.rb currency <entries.json> <carrier_entry_hash> <extent>' unless ARGV.size == 3
|
|
154
|
+
entries = load_json(ARGV[0], 'entries')
|
|
155
|
+
die "#{ARGV[0]}: entries must be a JSON array" unless entries.is_a?(Array)
|
|
156
|
+
carrier = ARGV[1].to_s
|
|
157
|
+
die 'carrier_entry_hash must be 64-char lowercase hex' unless carrier.match?(HEX64)
|
|
158
|
+
extent = strict_int(ARGV[2], 'extent')
|
|
159
|
+
scan = begin
|
|
160
|
+
SD.scan_currency(entries: entries, carrier_entry_hash: carrier, extent: extent)
|
|
161
|
+
rescue SD::DisclosureError => e
|
|
162
|
+
reject e.message
|
|
163
|
+
end
|
|
164
|
+
puts "REPORT: #{scan[:status]} (scanned extent #{scan[:scanned_extent]})"
|
|
165
|
+
puts " hits (positions): #{scan[:hits].join(', ')}" if scan[:hits]&.any?
|
|
166
|
+
puts " NOTE: #{scan[:note]}"
|
|
167
|
+
exit(scan[:status] == 'retracted' ? 1 : 0)
|
|
168
|
+
|
|
169
|
+
when 'convention'
|
|
170
|
+
puts "sdp-1 convention sha256: #{SD.convention_sha256}"
|
|
171
|
+
|
|
172
|
+
else
|
|
173
|
+
die 'usage: sdp_verify.rb commit|binding|presentation|profile|currency|convention …'
|
|
174
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# rpr-1 — KairosChain Reproduction Endorsement Convention, version 1
|
|
2
|
+
|
|
3
|
+
Status: committed verification convention (RPR-1..5 of
|
|
4
|
+
aud_l3_reproducibility_design v0.4). The SHA-256 over this file's raw bytes is
|
|
5
|
+
the convention integrity digest; every artifact that names `rpr-1` commits it
|
|
6
|
+
as its convention reference. A verifier holding a definition whose digest does
|
|
7
|
+
not match the committed one MUST treat the artifact's convention as
|
|
8
|
+
unresolvable and refuse to verify under it. Any change to this convention is a
|
|
9
|
+
new identifier (`rpr-2`, ...); this file is never edited in place.
|
|
10
|
+
rpr-1 builds on map-1 (credentials, attestation signatures, attestation types,
|
|
11
|
+
retraction) and khab-1 (committed identity, committed order) and modifies
|
|
12
|
+
neither: a map-1 artifact is valid with or without any rpr-1 artifact present.
|
|
13
|
+
|
|
14
|
+
All record literals below are canonical JSON (keys sorted recursively,
|
|
15
|
+
`JSON.generate` compact form). A record is valid only in its canonical
|
|
16
|
+
serialization with exactly the fields shown — a non-canonical or extra-field
|
|
17
|
+
record is not an rpr-1 artifact and is ignored as noise (map-1 §4 precedent).
|
|
18
|
+
|
|
19
|
+
## 1. Re-execution target (RPR-2)
|
|
20
|
+
|
|
21
|
+
{"environment_sha256":"<64-hex>","format":"rpr-1/target",
|
|
22
|
+
"input_sha256":"<64-hex>","output_sha256":"<64-hex>",
|
|
23
|
+
"pipeline_sha256":"<64-hex>"}
|
|
24
|
+
|
|
25
|
+
The target commits both halves of the verdict's referent: what is re-run
|
|
26
|
+
(inputs, environment, pipeline version) and what the re-run output is judged
|
|
27
|
+
against (the committed output). The first three digests — `input_sha256`,
|
|
28
|
+
`environment_sha256`, `pipeline_sha256` — constitute the target's **committed
|
|
29
|
+
computation identification** (design §3(b)): the identification of what is
|
|
30
|
+
re-run, which two targets can share while naming different committed outputs.
|
|
31
|
+
`output_sha256` completes the target but does not participate in that
|
|
32
|
+
identification. The target digest is the SHA-256 of the target's canonical
|
|
33
|
+
JSON. What the digests are digests OF (file formats, environment pinning,
|
|
34
|
+
pipeline identity) is the referenced material's business; rpr-1 commits
|
|
35
|
+
references, it does not validate referents (MPR-4 asymmetry, RPR-2 limit).
|
|
36
|
+
|
|
37
|
+
## 2. Tolerance declaration (RPR-3)
|
|
38
|
+
|
|
39
|
+
{"format":"rpr-1/tolerance","kind":"bit-identity","target_sha256":"<64-hex>"}
|
|
40
|
+
|
|
41
|
+
A tolerance declaration is bound to a named target and is result-free by
|
|
42
|
+
construction: the schema is closed and `kind` is the only expressive field, so
|
|
43
|
+
no outcome-referencing content can be expressed. At rpr-1 the only kind is
|
|
44
|
+
`bit-identity` (re-run output matches the committed output byte for byte);
|
|
45
|
+
richer tolerance grammars (numerical bounds, domain equivalence) are a new
|
|
46
|
+
convention — this narrowness is disclosed, not hidden. The declaration is
|
|
47
|
+
committed as an internal-chain record ahead of the endorsements it governs;
|
|
48
|
+
committed position provides decidable anteriority (MPR-8). One declaration has
|
|
49
|
+
exactly one digest (canonical serialization).
|
|
50
|
+
|
|
51
|
+
### 2.1 Declaration-set assessment (RPR-3)
|
|
52
|
+
|
|
53
|
+
Conformance of an endorsement's tolerance is assessed against the record, not
|
|
54
|
+
the endorser's selection. Given target records, tolerance declarations with
|
|
55
|
+
their committed positions, and the endorsement's committed position, the
|
|
56
|
+
assessment reports:
|
|
57
|
+
|
|
58
|
+
- every anterior declaration bound to the endorsement's target;
|
|
59
|
+
- every anterior declaration bound to any sibling target sharing the same
|
|
60
|
+
committed computation identification (§1) — a menu spread across
|
|
61
|
+
near-duplicate targets is the same menu and is read as one set (at rpr-1,
|
|
62
|
+
sharing = equality of the three computation-identification digests);
|
|
63
|
+
- the multiplicity of that pooled set and the invoked declaration's place in
|
|
64
|
+
it;
|
|
65
|
+
- the unresolved residue: declarations whose `target_sha256` resolves to no
|
|
66
|
+
supplied target record (sharing undecidable there), disclosed rather than
|
|
67
|
+
silently dropped.
|
|
68
|
+
|
|
69
|
+
The assessment reports; the reader prices. A verdict judged under one of many
|
|
70
|
+
anterior declarations is conforming and wears the multiplicity openly; a
|
|
71
|
+
tolerance chosen after the result is non-conforming (its posteriority is
|
|
72
|
+
decidable from committed order). The assessment is a function of the committed
|
|
73
|
+
record alone, never of presentation order: a declaration committed at several
|
|
74
|
+
positions is one declaration (one digest) with several commitments; the
|
|
75
|
+
invoked declaration conforms iff any of its commitments is anterior,
|
|
76
|
+
represented by the earliest, with posterior commitments disclosed alongside;
|
|
77
|
+
multiplicity counts distinct declarations, and the invoked declaration's
|
|
78
|
+
place is reported as its rank in the pooled anterior set ordered by earliest
|
|
79
|
+
anterior position, with equal positions ordered by declaration digest — so
|
|
80
|
+
every reported quantity, ranks included, is decidable from the committed
|
|
81
|
+
record alone.
|
|
82
|
+
|
|
83
|
+
## 3. Reproduction endorsement (RPR-1, RPR-4)
|
|
84
|
+
|
|
85
|
+
hand-adjudicated:
|
|
86
|
+
|
|
87
|
+
{"adjudication_mode":"hand","format":"rpr-1/endorsement",
|
|
88
|
+
"target_sha256":"<64-hex>","tolerance_sha256":"<64-hex>",
|
|
89
|
+
"verdict":"reproduced"}
|
|
90
|
+
|
|
91
|
+
procedure-adjudicated:
|
|
92
|
+
|
|
93
|
+
{"adjudication_mode":"procedure","format":"rpr-1/endorsement",
|
|
94
|
+
"procedure_sha256":"<64-hex>","target_sha256":"<64-hex>",
|
|
95
|
+
"tolerance_sha256":"<64-hex>","verdict":"not-reproduced"}
|
|
96
|
+
|
|
97
|
+
`verdict` is `reproduced` or `not-reproduced`: the affirmative and the
|
|
98
|
+
negative verdict are the same kind of record under the same bindings, so the
|
|
99
|
+
record has a conforming place for failure as well as success (RPR-1).
|
|
100
|
+
`adjudication_mode` names the gate behind the verdict (RPR-4): `hand`, or
|
|
101
|
+
`procedure` with `procedure_sha256` naming the committed adjudication
|
|
102
|
+
procedure whose anterior adoption is the judgment act. `procedure_sha256` is
|
|
103
|
+
present exactly when the mode is `procedure` (closed schema per mode). The
|
|
104
|
+
named mode, like the attestation type itself, is a declaration and not a
|
|
105
|
+
proof (map-1 §3 non-self-certifying).
|
|
106
|
+
|
|
107
|
+
The endorsement is signed under the endorser's map-1 credential using the
|
|
108
|
+
map-1 §1.1 attestation signature, with the payload being the endorsement
|
|
109
|
+
record string. Verification needs only the credential, the record, and the
|
|
110
|
+
signature. The endorsement is carried with `attestation_type` =
|
|
111
|
+
`quality-endorsement` (map-1 §3, unchanged).
|
|
112
|
+
|
|
113
|
+
Foreignness (RPR-4) is a conformance condition: an endorsement whose issuer
|
|
114
|
+
credential digest equals the operator credential digest for the target's
|
|
115
|
+
chain is not a conforming rpr-1 endorsement, whatever else it may be as an
|
|
116
|
+
ordinary same-party claim. Distinctness is not independence: a colluding pair
|
|
117
|
+
can fabricate an endorsement, affirmative or negative, without any run having
|
|
118
|
+
occurred — the endorsement is evidence of a committed claim, never proof that
|
|
119
|
+
a re-execution happened (RPR-4 disclosed limit).
|
|
120
|
+
|
|
121
|
+
What a verified endorsement asserts is exactly: this endorser claimed, at a
|
|
122
|
+
committed moment, that the target was re-run and its output did or did not
|
|
123
|
+
match the target's committed output within the named tolerance — and nothing
|
|
124
|
+
else. No claim of correctness, validity, fitness, or significance (RPR-1,
|
|
125
|
+
MPR-6); absence of any endorsement proves nothing (MPR-9 non-production).
|
|
126
|
+
|
|
127
|
+
## 4. Retraction (RPR-5)
|
|
128
|
+
|
|
129
|
+
A mistaken endorsement is withdrawn by the map-1 §3 `retraction` unchanged:
|
|
130
|
+
same issuer, unambiguous target, appended. A retracted endorsement still
|
|
131
|
+
stands as a committed record that the verdict was once claimed and later
|
|
132
|
+
taken back; nothing is unsaid by deletion and no anchored commitment's
|
|
133
|
+
coverage is subtracted (AHM-4).
|
|
134
|
+
|
|
135
|
+
## 5. Verification procedure and trust base
|
|
136
|
+
|
|
137
|
+
All rpr-1 verification needs only: the artifacts named above, this
|
|
138
|
+
definition, the map-1 definition (for credentials, signatures, types, and
|
|
139
|
+
retraction), and authentic views of the logs/chains the artifacts reference.
|
|
140
|
+
No registry, no network, no operator cooperation for verification —
|
|
141
|
+
production of artifacts remains the endorser's act (MPR-4 asymmetry).
|