aver-cert 0.1.0

Independent artifact certificate engine and verifier for Aver WebAssembly
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
/-
The semantic face of an accepted obligation is selected from the checked
claim and plan data.  The certificate may still provide source declarations
for models that cannot be reconstructed from Wasm, but it may not weaken the
standard domain, codomain, or representation relations of a known family.
-/
import AcceptedArtifactCore
import ConstructVerbatimSoundness
import FieldProjectionSoundness
import StringSoundness

namespace AverCert.StandardFace

open AverCert.Schema
open AverCert.AcceptedArtifact
open CertPrelude

/-- The complete host builder stored in an obligation. Faces bind the whole
    function, not a finite set of probes, so no unmentioned input can turn a
    claimed contract into a trap. -/
abbrev HostBuilder :=
  (List WVal → Option WVal) →
  (List WVal → Option WVal) →
  (List WVal → Option WVal) →
  (List WVal → Option WVal) →
  (Nat → List WVal → Option WVal) → HostTbl

def emptyHost : HostBuilder := fun _ _ _ _ _ _ => none

def decodedRoleIdx (roles : CertDecode.AddSub.Roles) : HostRole → Option Nat
  | .box => roles.box
  | .add => roles.add
  | .mul => roles.mul
  | .sub => roles.sub

/-- Every role/index pair used by a claim must agree with the unique table
    decoded from the module. Family checkers already require every role their
    plan consumes; distinct indices make the lookup extensional and reject
    duplicate or aliased role entries. -/
def hostTableBound
    (roles : CertDecode.AddSub.Roles)
    (hostTable : List (HostRole × Nat)) : Bool :=
  AverCert.PlanCheck.hostTableIndicesDistinct hostTable &&
    hostTable.all fun entry => decodedRoleIdx roles entry.1 == some entry.2

/-- A fully specified obligation face. `model? = none` keeps the source model
    as a read declaration; every other field remains fixed by the checked
    family. -/
structure FaceSpec where
  carrier : Nat
  Dom : Type
  Cod : Type
  domRepr : CarrierSpec carrier → Dom → List WVal → Prop
  codRepr : CarrierSpec carrier → Cod → WVal → Prop
  host : HostBuilder
  model? : Option (Dom → Cod) := none

/-- Known families have a complete standard face. User ADT reads retain only
    the parts that cannot be reconstructed without the source declaration. -/
inductive StandardFace where
  | known (spec : FaceSpec)
  | adtIntRead (host : HostBuilder)
  | adtConstructorRead (host : HostBuilder)

/-- Dependent fields are compared with `HEq`: ordinary equality cannot state
    the relation before the domain and codomain types have been identified. -/
def StandardFace.Matches : StandardFace → Obligation → Prop
  | .known spec, obligation =>
      obligation.carrier = spec.carrier ∧
      HEq obligation.Dom spec.Dom ∧
      HEq obligation.Cod spec.Cod ∧
      HEq obligation.domRepr spec.domRepr ∧
      HEq obligation.codRepr spec.codRepr ∧
      obligation.host = spec.host ∧
      match spec.model? with
      | some model => HEq obligation.model model
      | none => True
  | .adtIntRead host, obligation =>
      Nonempty obligation.Dom ∧
      HEq obligation.Cod Int ∧
      HEq obligation.codRepr (@intRepr obligation.carrier) ∧
      obligation.host = host
  | .adtConstructorRead host, obligation =>
      Nonempty obligation.Dom ∧ obligation.host = host

def intList (carrier arity : Nat) (host : HostBuilder) : FaceSpec where
  carrier := carrier
  Dom := List Int
  Cod := Int
  domRepr := fun S ns vs => ReprAll S.Repr ns vs ∧ ns.length = arity
  codRepr := intRepr
  host := host

def projection (carrier structIdx fieldIdx : Nat) : FaceSpec where
  carrier := carrier
  Dom := WVal × WVal
  Cod := WVal
  domRepr := fun _ p vs => vs = [.structv structIdx [p.1, p.2]]
  codRepr := verbatimRepr
  host := emptyHost
  model? := some (fun p =>
    FieldProjectionSoundness.pairProjection fieldIdx p.1 p.2)

def verbatim (carrier : Nat) (plan : VerbatimRawPlan) : FaceSpec where
  carrier := carrier
  Dom := WVal
  Cod := WVal
  domRepr := fun _ v vs => vs = [v]
  codRepr := verbatimRepr
  host := emptyHost
  model? := some (ConstructVerbatimSoundness.verbatimModel plan)

def stringEq
    (carrier stringTy helperIdx : Nat) (plan : StringEqRawPlan) : FaceSpec where
  carrier := carrier
  Dom := WVal
  Cod := WVal
  domRepr := fun _ v vs => vs = [v]
  codRepr := verbatimRepr
  host := stringEqCanonicalHost helperIdx
  model? := some (StringSoundness.evalStringEq stringTy plan)

def stringConcat
    (carrier resultTy containerTy helperIdx : Nat)
    (plan : StringConcatRawPlan) : FaceSpec where
  carrier := carrier
  Dom := WVal
  Cod := WVal
  domRepr := fun _ v vs => vs = [v]
  codRepr := verbatimRepr
  host := stringConcatCanonicalHost helperIdx resultTy
  model? := some (StringSoundness.evalStringConcat resultTy containerTy plan)

namespace FragTy

def denote : AverCert.Schema.FragTy → Type
  | .f64 => UInt64
  | .boolI32 => Bool
  | .intCarrier => Int
  | .i64 | .rawI32 | .ref | .adtRef => WVal

def encodeArg (carrier : Nat) :
    (ty : AverCert.Schema.FragTy) → FragTy.denote ty → WVal
  | .f64, bits => .f64v bits
  | .boolI32, value => b32 value
  | .intCarrier, value => carrierSmall carrier value
  | .i64, value | .rawI32, value | .ref, value | .adtRef, value => value

def resultRepr (carrier : Nat) :
    (ty : AverCert.Schema.FragTy) →
      CarrierSpec carrier → FragTy.denote ty → WVal → Prop
  | .f64 => floatBitsRepr
  | .boolI32 => boolRepr
  | .intCarrier => intRepr
  | .i64 | .rawI32 | .ref | .adtRef => verbatimRepr

end FragTy

namespace FragParams

/-- Right-associated product used by the emitted fragment obligations. -/
def denote : List AverCert.Schema.FragTy → Type
  | [] => Unit
  | [ty] => FragTy.denote ty
  | ty :: rest => FragTy.denote ty × denote rest

def encodeArgs (carrier : Nat) :
    (params : List AverCert.Schema.FragTy) → denote params → List WVal
  | [], _ => []
  | [ty], value => [FragTy.encodeArg carrier ty value]
  | ty :: next :: rest, values =>
      FragTy.encodeArg carrier ty values.1 ::
        encodeArgs carrier (next :: rest) values.2

end FragParams

def fragment
    (carrier : Nat)
    (params : List AverCert.Schema.FragTy)
    (result : AverCert.Schema.FragTy) : FaceSpec where
  carrier := carrier
  Dom := FragParams.denote params
  Cod := FragTy.denote result
  domRepr := fun _ values args => args = FragParams.encodeArgs carrier params values
  codRepr := FragTy.resultRepr carrier result
  host := emptyHost

def constructUnary (carrier structIdx : Nat) (plan : ConstructRawPlan) : FaceSpec where
  carrier := carrier
  Dom := WVal
  Cod := WVal
  domRepr := fun _ value args => args = [value]
  codRepr := verbatimRepr
  host := emptyHost
  model? := some (fun value => .structv structIdx
    (ConstructVerbatimSoundness.constructModelFields
      ([value] ++ List.replicate 1 .null) plan.fields))

def constructBinary (carrier structIdx : Nat) (plan : ConstructRawPlan) : FaceSpec where
  carrier := carrier
  Dom := WVal × WVal
  Cod := WVal
  domRepr := fun _ values args => args = [values.1, values.2]
  codRepr := verbatimRepr
  host := emptyHost
  model? := some (fun values => .structv structIdx
    (ConstructVerbatimSoundness.constructModelFields
      ([values.1, values.2] ++ List.replicate 1 .null) plan.fields))

structure IntAddFace where
  constant : Int
  boxIdx : Nat
  addIdx : Nat
deriving Repr, DecidableEq

def intAddHost (carrier : Nat) (face : IntAddFace) : HostBuilder :=
  fun add _ _ _ _ fn =>
    if fn = face.boxIdx then some (1, boxRef carrier)
    else if fn = face.addIdx then some (2, add)
    else none

/-- Exact `add(param0, box(k))` classifier. This family intentionally keeps
    the stronger `List Int`/`ReprAll` face instead of the small-value fragment
    representation. -/
def classifyIntAdd (plan : ExprFragmentRawPlan) : Option IntAddFace :=
  if plan.params = [.intCarrier] && plan.result = .intCarrier &&
      plan.body.result = 3 then
    match plan.body.nodes with
    | [n0, n1, n2, n3] =>
        match n0.kind, n1.kind, n2.kind, n3.kind with
        | .local 0, .constI64 constant,
          .hostCall .box boxIdx [1], .hostCall .add addIdx [0, 2] =>
            if n0.ty = .intCarrier && n1.ty = .i64 &&
                n2.ty = .intCarrier && n3.ty = .intCarrier then
              some { constant := constant, boxIdx := boxIdx, addIdx := addIdx }
            else none
        | _, _, _, _ => none
    | _ => none
  else none

/-- Generic fragments have no host-dependent or opaque-ADT face. Fuel
    exhaustion rejects rather than accidentally admitting a future deeper
    plan. Exact integer-add and projection plans are selected before this
    check. -/
def genericFragmentAllowedFuel : Nat → FragBlock → Bool
  | 0, _ => false
  | fuel + 1, block =>
      block.nodes.all fun node =>
        node.ty != .adtRef &&
        match node.kind with
        | .hostCall _ _ _ | .structGetUser _ _ _ => false
        | .ifElse _ thenBlock elseBlock =>
            genericFragmentAllowedFuel fuel thenBlock &&
              genericFragmentAllowedFuel fuel elseBlock
        | .selfCall _ _ _ => false
        | .local _ | .constBool _ | .constI64 _ | .constI32 _ |
          .constF64Bits _ | .structGet _ _ | .refIsNull _ |
          .prim _ _ => true

noncomputable def genericFragmentAllowed (plan : ExprFragmentRawPlan) : Bool :=
  !plan.params.contains .adtRef &&
    plan.result != .adtRef &&
    plan.result != .intCarrier &&
    genericFragmentAllowedFuel (sizeOf plan.body + 1) plan.body

noncomputable def symFragmentFace (claim : SymFragmentClaim) : Option StandardFace :=
  match AverCert.PlanCheck.encodeSymRawPlanToExprFragmentRawPlan
      claim.hostTable claim.structTable claim.plan with
  | none => none
  | some plan =>
      match classifyIntAdd plan with
      | some face =>
          some (.known
            (intList claim.carrier 1 (intAddHost claim.carrier face)))
      | none =>
          match AverCert.WasmSlice.exprProjectionFace? plan with
          | some (structIdx, fieldIdx) =>
              some (.known (projection claim.carrier structIdx fieldIdx))
          | none =>
              if genericFragmentAllowed plan then
                some (.known (fragment claim.carrier plan.params plan.result))
              else none

def symFragmentMatches
    (roles : CertDecode.AddSub.Roles) (claim : SymFragmentClaim) : Prop :=
  hostTableBound roles claim.hostTable = true ∧
    match symFragmentFace claim with
    | some face => face.Matches claim.obligation
    | none => False

def stringEqMatches (manifest : Manifest) (claim : StringEqClaim) : Prop :=
  match stringEqPlanForExport claim.exportName manifest.stringEqPlans with
  | some plan =>
      (StandardFace.known
        (stringEq claim.carrier claim.stringTy claim.stringEqFuncIdx plan)).Matches
          claim.obligation
  | none => False

def stringConcatMatches (manifest : Manifest) (claim : StringConcatClaim) : Prop :=
  match stringConcatPlanForExport claim.exportName manifest.stringConcatPlans with
  | some plan =>
      (StandardFace.known
        (stringConcat claim.carrier claim.resultTy claim.containerTy
          claim.concatFuncIdx plan)).Matches
          claim.obligation
  | none => False

def verbatimMatches (manifest : Manifest) (claim : VerbatimClaim) : Prop :=
  match verbatimPlanForExport claim.exportName manifest.verbatimPlans with
  | some plan =>
      (StandardFace.known (verbatim claim.carrier plan)).Matches claim.obligation
  | none => False

def fieldProjectionMatches
    (manifest : Manifest) (claim : FieldProjectionClaim) : Prop :=
  match fieldProjectionPlanForExport claim.exportName manifest.fieldProjectionPlans with
  | some plan =>
      (StandardFace.known
        (projection claim.carrier claim.structIdx plan.fieldIdx)).Matches claim.obligation
  | none => False

def constructMatches (manifest : Manifest) (claim : ConstructClaim) : Prop :=
  match constructPlanForExport claim.exportName manifest.constructPlans with
  | none => False
  | some plan =>
      match claim.symPlan.result with
      | .app1 "List" _ =>
          match plan.arity with
          | 1 =>
              (StandardFace.known
                (constructUnary claim.carrier claim.structIdx plan)).Matches
                  claim.obligation
          | 2 =>
              (StandardFace.known
                (constructBinary claim.carrier claim.structIdx plan)).Matches
                  claim.obligation
          | _ => False
      | .named _ =>
          (StandardFace.adtConstructorRead emptyHost).Matches claim.obligation
      | _ => False

def recursionMatches
    (manifest : Manifest) (roles : CertDecode.AddSub.Roles)
    (claim : RecursionClaim) : Prop :=
  hostTableBound roles claim.hostTable = true ∧
    match recursionPlanForExport claim.exportName manifest.recursionPlans with
    | some plan =>
        (StandardFace.known
          (intList claim.carrier plan.params.length
            (intDispatchCanonicalHost claim.carrier claim.hostTable))).Matches
              claim.obligation
    | none => False

def mutualMatches
    (manifest : Manifest) (roles : CertDecode.AddSub.Roles)
    (claim : MutualRecursionClaim) : Prop :=
  hostTableBound roles claim.hostTable = true ∧
    match mutualPlanForExport claim.exportName manifest.mutualPlans with
    | some _ =>
        (StandardFace.known
          (intList claim.carrier 1
            (intDispatchCanonicalHost claim.carrier claim.hostTable))).Matches
              claim.obligation
    | none => False

def intDispatchMatches
    (manifest : Manifest) (roles : CertDecode.AddSub.Roles)
    (claim : IntDispatchClaim) : Prop :=
  hostTableBound roles claim.hostTable = true ∧
    match intDispatchPlanForExport claim.exportName manifest.intDispatchPlans with
    | some _ =>
        (StandardFace.adtIntRead
          (intDispatchCanonicalHost claim.carrier claim.hostTable)).Matches
            claim.obligation
    | none => False

def compositionMatches
    (members : List CompositionMemberClaim)
    (roles : CertDecode.AddSub.Roles) (claim : CompositionClaim) : Prop :=
  hostTableBound roles claim.hostTable = true ∧
    match compositionMemberForName claim.exportName members with
    | some _ =>
        (StandardFace.known
          (intList claim.carrier 1
            (intDispatchCanonicalHost claim.carrier claim.hostTable))).Matches
              claim.obligation
    | none => False

/-- Cross-family uniqueness closes the gap left by uniqueness within individual
    plan lists: one obligation export may be claimed by exactly one family. -/
def claimExportsUnique (artifact : ArtifactData) : Bool :=
  AverCert.WasmSlice.indexedNodup (claimObligationExports artifact)

/-- Report one fixed class for every claim in a family. The export and class
    stay paired throughout; the verifier never compares two independently
    ordered lists. -/
def fixedReportEntries {Claim : Type u}
    (className : String) (obligation : Claim → Obligation) :
    List Claim → List (String × String) :=
  List.map fun claim => ((obligation claim).export_, className)

def recursionReportEntry
    (manifest : Manifest) (claim : RecursionClaim) : Option (String × String) := do
  let plan ← recursionPlanForExport claim.exportName manifest.recursionPlans
  let className ← match plan.params with
    | [_] => some "self-recursive"
    | [_, _] => some "multi-argument self-recursive"
    | _ => none
  pure (claim.obligation.export_, className)

/-- Derive every public class label from the checked claim family and plan.
    This is report data only, but deriving it in the wall prevents the package
    producer from choosing a more favourable label for an accepted obligation. -/
def claimReportEntries (artifact : ArtifactData) : Option (List (String × String)) := do
  let recursion ← artifact.recursionClaims.mapM
    (recursionReportEntry artifact.manifest)
  pure <|
    fixedReportEntries "expr-fragment-v1"
      (fun c : SymFragmentClaim => c.obligation) artifact.symFragmentClaims ++
    fixedReportEntries "verbatim-string-eq"
      (fun c : StringEqClaim => c.obligation) artifact.stringEqClaims ++
    fixedReportEntries "verbatim-string-concat"
      (fun c : StringConcatClaim => c.obligation) artifact.stringConcatClaims ++
    fixedReportEntries "adt-constructor"
      (fun c : ConstructClaim => c.obligation) artifact.constructClaims ++
    recursion ++
    fixedReportEntries "mutual-recursive"
      (fun c : MutualRecursionClaim => c.obligation) artifact.mutualRecursionClaims ++
    fixedReportEntries "verbatim-dispatch"
      (fun c : VerbatimClaim => c.obligation) artifact.verbatimClaims ++
    fixedReportEntries "int-dispatch"
      (fun c : IntDispatchClaim => c.obligation) artifact.intDispatchClaims ++
    fixedReportEntries "field-projection"
      (fun c : FieldProjectionClaim => c.obligation) artifact.fieldProjectionClaims ++
    fixedReportEntries "cross-function-composition"
      (fun c : CompositionClaim => c.obligation) artifact.compositionClaims

def reportEntryFor
    (entries : List (String × String)) (obligation : Obligation) :
    Option (String × String) := do
  let className ← namedPlanForExport obligation.export_ entries
  pure (obligation.export_, className)

/-- Public report entries in manifest order. Cross-family uniqueness is checked
    before lookup, so a label can never be selected by first-match ambiguity. -/
def reportEntries (artifact : ArtifactData) : Option (List (String × String)) :=
  if claimExportsUnique artifact then do
    let entries ← claimReportEntries artifact
    artifact.manifest.obligations.mapM (reportEntryFor entries)
  else none

/-- Every claim is unique across families and carries the semantic face selected
    by its checked family and plan. This is deliberately conjoined with the
    established byte-origin predicates; moving reconstruction into Lean does
    not remove any existing acceptance gate. -/
def checkedFaces (artifact : ArtifactData) : Prop :=
  claimExportsUnique artifact = true ∧
  allClaims (symFragmentMatches artifact.manifest.subject.hostRoleTable)
    artifact.symFragmentClaims ∧
  allClaims (stringEqMatches artifact.manifest) artifact.stringEqClaims ∧
  allClaims (stringConcatMatches artifact.manifest) artifact.stringConcatClaims ∧
  allClaims (constructMatches artifact.manifest) artifact.constructClaims ∧
  allClaims (recursionMatches artifact.manifest
    artifact.manifest.subject.hostRoleTable) artifact.recursionClaims ∧
  allClaims (mutualMatches artifact.manifest
    artifact.manifest.subject.hostRoleTable) artifact.mutualRecursionClaims ∧
  allClaims (verbatimMatches artifact.manifest) artifact.verbatimClaims ∧
  allClaims (intDispatchMatches artifact.manifest
    artifact.manifest.subject.hostRoleTable) artifact.intDispatchClaims ∧
  allClaims (fieldProjectionMatches artifact.manifest) artifact.fieldProjectionClaims ∧
  allClaims (compositionMatches artifact.compositionMembers
    artifact.manifest.subject.hostRoleTable) artifact.compositionClaims

end AverCert.StandardFace