consiliency-canon-core 0.1.0__tar.gz
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.
- consiliency_canon_core-0.1.0/.gitignore +5 -0
- consiliency_canon_core-0.1.0/Cargo.lock +408 -0
- consiliency_canon_core-0.1.0/Cargo.toml +23 -0
- consiliency_canon_core-0.1.0/Makefile.packaging +38 -0
- consiliency_canon_core-0.1.0/PACKAGING.md +23 -0
- consiliency_canon_core-0.1.0/PKG-INFO +43 -0
- consiliency_canon_core-0.1.0/pyproject.toml +41 -0
- consiliency_canon_core-0.1.0/src/bin/canon_core_emit.rs +36 -0
- consiliency_canon_core-0.1.0/src/lib.rs +275 -0
- consiliency_canon_core-0.1.0/tests/parity.rs +39 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "autocfg"
|
|
7
|
+
version = "1.5.1"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
|
10
|
+
|
|
11
|
+
[[package]]
|
|
12
|
+
name = "base64"
|
|
13
|
+
version = "0.22.1"
|
|
14
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
15
|
+
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
|
16
|
+
|
|
17
|
+
[[package]]
|
|
18
|
+
name = "block-buffer"
|
|
19
|
+
version = "0.10.4"
|
|
20
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
21
|
+
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
|
22
|
+
dependencies = [
|
|
23
|
+
"generic-array",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[[package]]
|
|
27
|
+
name = "bumpalo"
|
|
28
|
+
version = "3.20.3"
|
|
29
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
30
|
+
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
|
31
|
+
|
|
32
|
+
[[package]]
|
|
33
|
+
name = "canon-core"
|
|
34
|
+
version = "0.1.0"
|
|
35
|
+
dependencies = [
|
|
36
|
+
"base64",
|
|
37
|
+
"hex",
|
|
38
|
+
"num-bigint",
|
|
39
|
+
"pyo3",
|
|
40
|
+
"serde_json",
|
|
41
|
+
"sha2",
|
|
42
|
+
"wasm-bindgen",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[[package]]
|
|
46
|
+
name = "cfg-if"
|
|
47
|
+
version = "1.0.4"
|
|
48
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
49
|
+
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
50
|
+
|
|
51
|
+
[[package]]
|
|
52
|
+
name = "cpufeatures"
|
|
53
|
+
version = "0.2.17"
|
|
54
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
55
|
+
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
|
56
|
+
dependencies = [
|
|
57
|
+
"libc",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[[package]]
|
|
61
|
+
name = "crypto-common"
|
|
62
|
+
version = "0.1.7"
|
|
63
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
64
|
+
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
|
65
|
+
dependencies = [
|
|
66
|
+
"generic-array",
|
|
67
|
+
"typenum",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
[[package]]
|
|
71
|
+
name = "digest"
|
|
72
|
+
version = "0.10.7"
|
|
73
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
74
|
+
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
|
75
|
+
dependencies = [
|
|
76
|
+
"block-buffer",
|
|
77
|
+
"crypto-common",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
[[package]]
|
|
81
|
+
name = "generic-array"
|
|
82
|
+
version = "0.14.7"
|
|
83
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
84
|
+
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
|
85
|
+
dependencies = [
|
|
86
|
+
"typenum",
|
|
87
|
+
"version_check",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
[[package]]
|
|
91
|
+
name = "heck"
|
|
92
|
+
version = "0.5.0"
|
|
93
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
94
|
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
95
|
+
|
|
96
|
+
[[package]]
|
|
97
|
+
name = "hex"
|
|
98
|
+
version = "0.4.3"
|
|
99
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
100
|
+
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
|
101
|
+
|
|
102
|
+
[[package]]
|
|
103
|
+
name = "indoc"
|
|
104
|
+
version = "2.0.7"
|
|
105
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
106
|
+
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
|
107
|
+
dependencies = [
|
|
108
|
+
"rustversion",
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
[[package]]
|
|
112
|
+
name = "itoa"
|
|
113
|
+
version = "1.0.18"
|
|
114
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
115
|
+
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
116
|
+
|
|
117
|
+
[[package]]
|
|
118
|
+
name = "libc"
|
|
119
|
+
version = "0.2.186"
|
|
120
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
121
|
+
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
|
122
|
+
|
|
123
|
+
[[package]]
|
|
124
|
+
name = "memchr"
|
|
125
|
+
version = "2.8.2"
|
|
126
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
127
|
+
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
|
128
|
+
|
|
129
|
+
[[package]]
|
|
130
|
+
name = "memoffset"
|
|
131
|
+
version = "0.9.1"
|
|
132
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
133
|
+
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
|
134
|
+
dependencies = [
|
|
135
|
+
"autocfg",
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
[[package]]
|
|
139
|
+
name = "num-bigint"
|
|
140
|
+
version = "0.4.6"
|
|
141
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
142
|
+
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
|
143
|
+
dependencies = [
|
|
144
|
+
"num-integer",
|
|
145
|
+
"num-traits",
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
[[package]]
|
|
149
|
+
name = "num-integer"
|
|
150
|
+
version = "0.1.46"
|
|
151
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
152
|
+
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
|
153
|
+
dependencies = [
|
|
154
|
+
"num-traits",
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
[[package]]
|
|
158
|
+
name = "num-traits"
|
|
159
|
+
version = "0.2.19"
|
|
160
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
161
|
+
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
|
162
|
+
dependencies = [
|
|
163
|
+
"autocfg",
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
[[package]]
|
|
167
|
+
name = "once_cell"
|
|
168
|
+
version = "1.21.4"
|
|
169
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
170
|
+
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
171
|
+
|
|
172
|
+
[[package]]
|
|
173
|
+
name = "portable-atomic"
|
|
174
|
+
version = "1.13.1"
|
|
175
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
176
|
+
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
|
177
|
+
|
|
178
|
+
[[package]]
|
|
179
|
+
name = "proc-macro2"
|
|
180
|
+
version = "1.0.106"
|
|
181
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
182
|
+
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
|
183
|
+
dependencies = [
|
|
184
|
+
"unicode-ident",
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
[[package]]
|
|
188
|
+
name = "pyo3"
|
|
189
|
+
version = "0.23.5"
|
|
190
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
191
|
+
checksum = "7778bffd85cf38175ac1f545509665d0b9b92a198ca7941f131f85f7a4f9a872"
|
|
192
|
+
dependencies = [
|
|
193
|
+
"cfg-if",
|
|
194
|
+
"indoc",
|
|
195
|
+
"libc",
|
|
196
|
+
"memoffset",
|
|
197
|
+
"once_cell",
|
|
198
|
+
"portable-atomic",
|
|
199
|
+
"pyo3-build-config",
|
|
200
|
+
"pyo3-ffi",
|
|
201
|
+
"pyo3-macros",
|
|
202
|
+
"unindent",
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
[[package]]
|
|
206
|
+
name = "pyo3-build-config"
|
|
207
|
+
version = "0.23.5"
|
|
208
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
209
|
+
checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb"
|
|
210
|
+
dependencies = [
|
|
211
|
+
"once_cell",
|
|
212
|
+
"target-lexicon",
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
[[package]]
|
|
216
|
+
name = "pyo3-ffi"
|
|
217
|
+
version = "0.23.5"
|
|
218
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
219
|
+
checksum = "e9f1b4c431c0bb1c8fb0a338709859eed0d030ff6daa34368d3b152a63dfdd8d"
|
|
220
|
+
dependencies = [
|
|
221
|
+
"libc",
|
|
222
|
+
"pyo3-build-config",
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
[[package]]
|
|
226
|
+
name = "pyo3-macros"
|
|
227
|
+
version = "0.23.5"
|
|
228
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
229
|
+
checksum = "fbc2201328f63c4710f68abdf653c89d8dbc2858b88c5d88b0ff38a75288a9da"
|
|
230
|
+
dependencies = [
|
|
231
|
+
"proc-macro2",
|
|
232
|
+
"pyo3-macros-backend",
|
|
233
|
+
"quote",
|
|
234
|
+
"syn",
|
|
235
|
+
]
|
|
236
|
+
|
|
237
|
+
[[package]]
|
|
238
|
+
name = "pyo3-macros-backend"
|
|
239
|
+
version = "0.23.5"
|
|
240
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
241
|
+
checksum = "fca6726ad0f3da9c9de093d6f116a93c1a38e417ed73bf138472cf4064f72028"
|
|
242
|
+
dependencies = [
|
|
243
|
+
"heck",
|
|
244
|
+
"proc-macro2",
|
|
245
|
+
"pyo3-build-config",
|
|
246
|
+
"quote",
|
|
247
|
+
"syn",
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
[[package]]
|
|
251
|
+
name = "quote"
|
|
252
|
+
version = "1.0.46"
|
|
253
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
254
|
+
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
|
|
255
|
+
dependencies = [
|
|
256
|
+
"proc-macro2",
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
[[package]]
|
|
260
|
+
name = "rustversion"
|
|
261
|
+
version = "1.0.22"
|
|
262
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
263
|
+
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|
264
|
+
|
|
265
|
+
[[package]]
|
|
266
|
+
name = "serde"
|
|
267
|
+
version = "1.0.228"
|
|
268
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
269
|
+
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
|
270
|
+
dependencies = [
|
|
271
|
+
"serde_core",
|
|
272
|
+
]
|
|
273
|
+
|
|
274
|
+
[[package]]
|
|
275
|
+
name = "serde_core"
|
|
276
|
+
version = "1.0.228"
|
|
277
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
278
|
+
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
|
279
|
+
dependencies = [
|
|
280
|
+
"serde_derive",
|
|
281
|
+
]
|
|
282
|
+
|
|
283
|
+
[[package]]
|
|
284
|
+
name = "serde_derive"
|
|
285
|
+
version = "1.0.228"
|
|
286
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
287
|
+
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
288
|
+
dependencies = [
|
|
289
|
+
"proc-macro2",
|
|
290
|
+
"quote",
|
|
291
|
+
"syn",
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
[[package]]
|
|
295
|
+
name = "serde_json"
|
|
296
|
+
version = "1.0.150"
|
|
297
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
298
|
+
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
|
299
|
+
dependencies = [
|
|
300
|
+
"itoa",
|
|
301
|
+
"memchr",
|
|
302
|
+
"serde",
|
|
303
|
+
"serde_core",
|
|
304
|
+
"zmij",
|
|
305
|
+
]
|
|
306
|
+
|
|
307
|
+
[[package]]
|
|
308
|
+
name = "sha2"
|
|
309
|
+
version = "0.10.9"
|
|
310
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
311
|
+
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
|
312
|
+
dependencies = [
|
|
313
|
+
"cfg-if",
|
|
314
|
+
"cpufeatures",
|
|
315
|
+
"digest",
|
|
316
|
+
]
|
|
317
|
+
|
|
318
|
+
[[package]]
|
|
319
|
+
name = "syn"
|
|
320
|
+
version = "2.0.118"
|
|
321
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
322
|
+
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
|
|
323
|
+
dependencies = [
|
|
324
|
+
"proc-macro2",
|
|
325
|
+
"quote",
|
|
326
|
+
"unicode-ident",
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
[[package]]
|
|
330
|
+
name = "target-lexicon"
|
|
331
|
+
version = "0.12.16"
|
|
332
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
333
|
+
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
|
334
|
+
|
|
335
|
+
[[package]]
|
|
336
|
+
name = "typenum"
|
|
337
|
+
version = "1.20.1"
|
|
338
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
339
|
+
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
|
340
|
+
|
|
341
|
+
[[package]]
|
|
342
|
+
name = "unicode-ident"
|
|
343
|
+
version = "1.0.24"
|
|
344
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
345
|
+
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
346
|
+
|
|
347
|
+
[[package]]
|
|
348
|
+
name = "unindent"
|
|
349
|
+
version = "0.2.4"
|
|
350
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
351
|
+
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
|
352
|
+
|
|
353
|
+
[[package]]
|
|
354
|
+
name = "version_check"
|
|
355
|
+
version = "0.9.5"
|
|
356
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
357
|
+
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|
358
|
+
|
|
359
|
+
[[package]]
|
|
360
|
+
name = "wasm-bindgen"
|
|
361
|
+
version = "0.2.126"
|
|
362
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
363
|
+
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
|
|
364
|
+
dependencies = [
|
|
365
|
+
"cfg-if",
|
|
366
|
+
"once_cell",
|
|
367
|
+
"rustversion",
|
|
368
|
+
"wasm-bindgen-macro",
|
|
369
|
+
"wasm-bindgen-shared",
|
|
370
|
+
]
|
|
371
|
+
|
|
372
|
+
[[package]]
|
|
373
|
+
name = "wasm-bindgen-macro"
|
|
374
|
+
version = "0.2.126"
|
|
375
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
376
|
+
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
|
|
377
|
+
dependencies = [
|
|
378
|
+
"quote",
|
|
379
|
+
"wasm-bindgen-macro-support",
|
|
380
|
+
]
|
|
381
|
+
|
|
382
|
+
[[package]]
|
|
383
|
+
name = "wasm-bindgen-macro-support"
|
|
384
|
+
version = "0.2.126"
|
|
385
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
386
|
+
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
|
|
387
|
+
dependencies = [
|
|
388
|
+
"bumpalo",
|
|
389
|
+
"proc-macro2",
|
|
390
|
+
"quote",
|
|
391
|
+
"syn",
|
|
392
|
+
"wasm-bindgen-shared",
|
|
393
|
+
]
|
|
394
|
+
|
|
395
|
+
[[package]]
|
|
396
|
+
name = "wasm-bindgen-shared"
|
|
397
|
+
version = "0.2.126"
|
|
398
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
399
|
+
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
|
|
400
|
+
dependencies = [
|
|
401
|
+
"unicode-ident",
|
|
402
|
+
]
|
|
403
|
+
|
|
404
|
+
[[package]]
|
|
405
|
+
name = "zmij"
|
|
406
|
+
version = "1.0.21"
|
|
407
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
408
|
+
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "canon-core"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
license = "MIT"
|
|
6
|
+
|
|
7
|
+
[lib]
|
|
8
|
+
name = "canon_core"
|
|
9
|
+
crate-type = ["rlib", "cdylib"]
|
|
10
|
+
|
|
11
|
+
[features]
|
|
12
|
+
default = []
|
|
13
|
+
pyo3-binding = ["dep:pyo3"]
|
|
14
|
+
wasm-binding = ["dep:wasm-bindgen"]
|
|
15
|
+
|
|
16
|
+
[dependencies]
|
|
17
|
+
base64 = "0.22"
|
|
18
|
+
hex = "0.4"
|
|
19
|
+
num-bigint = "0.4"
|
|
20
|
+
serde_json = "1"
|
|
21
|
+
sha2 = "0.10"
|
|
22
|
+
pyo3 = { version = "0.23", features = ["extension-module"], optional = true }
|
|
23
|
+
wasm-bindgen = { version = "0.2", optional = true }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# PKG (GP-canon chapter): dual-build canon-core for PyPI (maturin) + npm (wasm-pack).
|
|
2
|
+
# The shared conformance corpus is staged into the crate dir so both artifacts ship it.
|
|
3
|
+
#
|
|
4
|
+
# Prereqs: rustup, maturin (pipx install maturin), wasm-pack (cargo install wasm-pack).
|
|
5
|
+
# Publish is done by CI (publish-canon-core.yml) via OIDC — these targets are for local
|
|
6
|
+
# build + verification.
|
|
7
|
+
|
|
8
|
+
SCOPE := consiliency
|
|
9
|
+
NPM_NAME := @$(SCOPE)/canon-core
|
|
10
|
+
CORPUS_SRC := ../vectors/canon-vectors.json
|
|
11
|
+
CORPUS := canon-vectors.json
|
|
12
|
+
|
|
13
|
+
.PHONY: all stage-corpus wheel wasm verify clean
|
|
14
|
+
|
|
15
|
+
all: wheel wasm
|
|
16
|
+
|
|
17
|
+
# Stage the shared corpus into the crate dir so maturin `include` + wasm pkg can ship it.
|
|
18
|
+
stage-corpus:
|
|
19
|
+
cp $(CORPUS_SRC) $(CORPUS)
|
|
20
|
+
|
|
21
|
+
# PyPI wheel (pyo3 extension module `canon_core`).
|
|
22
|
+
wheel: stage-corpus
|
|
23
|
+
maturin build --release --features pyo3-binding --manifest-path Cargo.toml --out dist
|
|
24
|
+
|
|
25
|
+
# npm package @consiliency/canon-core, Node target (gp consumes this), wasm-binding feature.
|
|
26
|
+
# Corpus copied into the generated pkg/ so `@consiliency/canon-core/canon-vectors.json` resolves.
|
|
27
|
+
wasm: stage-corpus
|
|
28
|
+
wasm-pack build --release --scope $(SCOPE) --target nodejs --out-dir pkg -- --features wasm-binding
|
|
29
|
+
cp $(CORPUS) pkg/$(CORPUS)
|
|
30
|
+
|
|
31
|
+
# Local parity smoke: the built wheel + wasm pkg must reproduce the corpus bytes/digests.
|
|
32
|
+
# (Full cross-engine gate is GATE phase: conformance/check_xg4_canon_core.sh extended to run
|
|
33
|
+
# the BUILT artifacts, not cargo check.)
|
|
34
|
+
verify:
|
|
35
|
+
@echo "run: python -c 'import canon_core; ...' and node -e 'require(\"./pkg\")...' against $(CORPUS)"
|
|
36
|
+
|
|
37
|
+
clean:
|
|
38
|
+
rm -rf dist pkg target $(CORPUS)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# canon-core packaging (PKG)
|
|
2
|
+
|
|
3
|
+
Dual-published canonicalization engine — the shared byte/digest truth every Consiliency
|
|
4
|
+
certificate hashes. Same Rust engine (`src/lib.rs`), two distributions:
|
|
5
|
+
|
|
6
|
+
| Distribution | Tool | Name | Consumer | Entry points |
|
|
7
|
+
|---|---|---|---|---|
|
|
8
|
+
| PyPI | maturin (`pyproject.toml`) | `consiliency-canon-core` (imports as `canon_core`) | Python | `canonical_bytes_from_json(tagged_json)`, `digest_from_json(tagged_json, profile)` |
|
|
9
|
+
| npm | wasm-pack (`Makefile.packaging`) | `@consiliency/canon-core` (Node target) | governed-pipeline (JS) | `canonicalBytesFromJson(taggedJson)`, `digestFromJson(taggedJson, profile)` |
|
|
10
|
+
|
|
11
|
+
**Surface is tagged-JSON *text*, not a raw value** — this is load-bearing for byte-identity:
|
|
12
|
+
a lone surrogate serializes to the ASCII escape `\ud800`, survives UTF-8 marshalling, and
|
|
13
|
+
`serde_json` rejects it at parse (SPEC §5.0). Consumers MUST feed tagged-JSON text produced
|
|
14
|
+
by a validating encoder, never a raw host string (see the GP-canon design doc). Both packages
|
|
15
|
+
ship the shared `canon-vectors.json` corpus so consumers can self-verify parity.
|
|
16
|
+
|
|
17
|
+
## Local build
|
|
18
|
+
make -f Makefile.packaging all # needs: maturin, wasm-pack, wasm32 target
|
|
19
|
+
|
|
20
|
+
## Publish
|
|
21
|
+
Push a `canon-core-v<version>` tag → `.github/workflows/publish-canon-core.yml` builds +
|
|
22
|
+
publishes both via OIDC. One-time registry setup (see the workflow header) is a USER action:
|
|
23
|
+
claim the PyPI project + the npm `@consiliency` scope and register the trusted publishers.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: consiliency-canon-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Classifier: Programming Language :: Python :: 3
|
|
5
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
8
|
+
Classifier: Programming Language :: Rust
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Security :: Cryptography
|
|
11
|
+
Summary: Canon v2 canonicalization engine — the shared byte/digest truth for Consiliency certificates
|
|
12
|
+
Keywords: canonicalization,consiliency,canon,digest
|
|
13
|
+
Author: Consiliency
|
|
14
|
+
License-Expression: MIT
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
17
|
+
Project-URL: Homepage, https://github.com/Consiliency/spec
|
|
18
|
+
Project-URL: Repository, https://github.com/Consiliency/spec
|
|
19
|
+
|
|
20
|
+
# canon-core packaging (PKG)
|
|
21
|
+
|
|
22
|
+
Dual-published canonicalization engine — the shared byte/digest truth every Consiliency
|
|
23
|
+
certificate hashes. Same Rust engine (`src/lib.rs`), two distributions:
|
|
24
|
+
|
|
25
|
+
| Distribution | Tool | Name | Consumer | Entry points |
|
|
26
|
+
|---|---|---|---|---|
|
|
27
|
+
| PyPI | maturin (`pyproject.toml`) | `consiliency-canon-core` (imports as `canon_core`) | Python | `canonical_bytes_from_json(tagged_json)`, `digest_from_json(tagged_json, profile)` |
|
|
28
|
+
| npm | wasm-pack (`Makefile.packaging`) | `@consiliency/canon-core` (Node target) | governed-pipeline (JS) | `canonicalBytesFromJson(taggedJson)`, `digestFromJson(taggedJson, profile)` |
|
|
29
|
+
|
|
30
|
+
**Surface is tagged-JSON *text*, not a raw value** — this is load-bearing for byte-identity:
|
|
31
|
+
a lone surrogate serializes to the ASCII escape `\ud800`, survives UTF-8 marshalling, and
|
|
32
|
+
`serde_json` rejects it at parse (SPEC §5.0). Consumers MUST feed tagged-JSON text produced
|
|
33
|
+
by a validating encoder, never a raw host string (see the GP-canon design doc). Both packages
|
|
34
|
+
ship the shared `canon-vectors.json` corpus so consumers can self-verify parity.
|
|
35
|
+
|
|
36
|
+
## Local build
|
|
37
|
+
make -f Makefile.packaging all # needs: maturin, wasm-pack, wasm32 target
|
|
38
|
+
|
|
39
|
+
## Publish
|
|
40
|
+
Push a `canon-core-v<version>` tag → `.github/workflows/publish-canon-core.yml` builds +
|
|
41
|
+
publishes both via OIDC. One-time registry setup (see the workflow header) is a USER action:
|
|
42
|
+
claim the PyPI project + the npm `@consiliency` scope and register the trusted publishers.
|
|
43
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# PKG (GP-canon chapter): maturin build for the PyPI distribution of canon-core.
|
|
2
|
+
# Builds the pyo3-binding feature into an importable extension module `canon_core`
|
|
3
|
+
# exposing canonical_bytes_from_json(tagged_json) + digest_from_json(tagged_json, profile).
|
|
4
|
+
# The npm/WASM sibling is built by wasm-pack (see Makefile.packaging); both are published
|
|
5
|
+
# by .github/workflows/publish-canon-core.yml via OIDC trusted publishing.
|
|
6
|
+
[build-system]
|
|
7
|
+
requires = ["maturin>=1.5,<2"]
|
|
8
|
+
build-backend = "maturin"
|
|
9
|
+
|
|
10
|
+
[project]
|
|
11
|
+
name = "consiliency-canon-core"
|
|
12
|
+
version = "0.1.0"
|
|
13
|
+
description = "Canon v2 canonicalization engine — the shared byte/digest truth for Consiliency certificates"
|
|
14
|
+
readme = "PACKAGING.md"
|
|
15
|
+
requires-python = ">=3.10"
|
|
16
|
+
license = "MIT"
|
|
17
|
+
authors = [{ name = "Consiliency" }]
|
|
18
|
+
keywords = ["canonicalization", "consiliency", "canon", "digest"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Rust",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"Topic :: Security :: Cryptography",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/Consiliency/spec"
|
|
31
|
+
Repository = "https://github.com/Consiliency/spec"
|
|
32
|
+
|
|
33
|
+
[tool.maturin]
|
|
34
|
+
# The Rust pymodule is `canon_core` (src/lib.rs #[pymodule] fn canon_core); the PyPI
|
|
35
|
+
# dist name is consiliency-canon-core, so pin the import module-name explicitly.
|
|
36
|
+
module-name = "canon_core"
|
|
37
|
+
features = ["pyo3-binding"]
|
|
38
|
+
manifest-path = "Cargo.toml"
|
|
39
|
+
# Ship the shared conformance corpus INSIDE the wheel so a pinned install can self-verify
|
|
40
|
+
# byte/digest parity against the same 36 vectors the Rust/TS/Python engines share.
|
|
41
|
+
include = [{ path = "canon-vectors.json", format = "wheel" }]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
use base64::engine::general_purpose::STANDARD;
|
|
2
|
+
use base64::Engine;
|
|
3
|
+
use canon_core::{canonical_bytes, decode_input, digest, parse_vector_corpus};
|
|
4
|
+
use serde_json::Value;
|
|
5
|
+
use std::env;
|
|
6
|
+
use std::fs;
|
|
7
|
+
|
|
8
|
+
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
9
|
+
let vector_path = env::args()
|
|
10
|
+
.nth(1)
|
|
11
|
+
.unwrap_or_else(|| "../../vectors/canon-vectors.json".to_string());
|
|
12
|
+
let raw = fs::read_to_string(vector_path)?;
|
|
13
|
+
let vectors: Vec<Value> = parse_vector_corpus(&raw)?;
|
|
14
|
+
let mut lines = Vec::with_capacity(vectors.len());
|
|
15
|
+
for vector in vectors {
|
|
16
|
+
let name = vector["name"].as_str().ok_or("vector missing name")?;
|
|
17
|
+
let value = decode_input(&vector["input"])?;
|
|
18
|
+
if vector.get("expect_error").and_then(Value::as_bool).unwrap_or(false) {
|
|
19
|
+
match canonical_bytes(&value) {
|
|
20
|
+
Ok(bytes) => {
|
|
21
|
+
return Err(format!("expected CanonError but encoding succeeded for {name}: {bytes:?}").into());
|
|
22
|
+
}
|
|
23
|
+
Err(_) => lines.push(format!("{name}\tERROR\tERROR")),
|
|
24
|
+
}
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
let profile = vector["profile"].as_str().ok_or("vector missing profile")?;
|
|
28
|
+
let bytes = canonical_bytes(&value)?;
|
|
29
|
+
let bytes_b64 = STANDARD.encode(bytes);
|
|
30
|
+
let digest_hex = digest(&value, profile)?;
|
|
31
|
+
lines.push(format!("{name}\t{bytes_b64}\t{digest_hex}"));
|
|
32
|
+
}
|
|
33
|
+
lines.sort();
|
|
34
|
+
println!("{}", lines.join("\n"));
|
|
35
|
+
Ok(())
|
|
36
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
use num_bigint::BigInt;
|
|
2
|
+
use serde_json::Value;
|
|
3
|
+
use sha2::{Digest, Sha256};
|
|
4
|
+
use std::cmp::Ordering;
|
|
5
|
+
use std::fmt;
|
|
6
|
+
|
|
7
|
+
pub const DOMAIN_PREFIX: &str = "spec-canon:v2:";
|
|
8
|
+
pub const PROFILES: [&str; 4] = ["semantic-content", "run", "artifact-byte", "certificate"];
|
|
9
|
+
|
|
10
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
11
|
+
pub enum CanonValue {
|
|
12
|
+
Null,
|
|
13
|
+
Bool(bool),
|
|
14
|
+
Int(BigInt),
|
|
15
|
+
String(String),
|
|
16
|
+
Array(Vec<CanonValue>),
|
|
17
|
+
Object(Vec<(String, CanonValue)>),
|
|
18
|
+
FloatMarker,
|
|
19
|
+
NanMarker,
|
|
20
|
+
InfMarker,
|
|
21
|
+
SurrogateMarker,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
25
|
+
pub struct CanonError {
|
|
26
|
+
message: String,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl CanonError {
|
|
30
|
+
fn new(message: impl Into<String>) -> Self {
|
|
31
|
+
Self { message: message.into() }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl fmt::Display for CanonError {
|
|
36
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
37
|
+
f.write_str(&self.message)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
impl std::error::Error for CanonError {}
|
|
42
|
+
|
|
43
|
+
pub type CanonResult<T> = Result<T, CanonError>;
|
|
44
|
+
|
|
45
|
+
fn compare_code_points(a: &str, b: &str) -> Ordering {
|
|
46
|
+
let mut ai = a.chars();
|
|
47
|
+
let mut bi = b.chars();
|
|
48
|
+
loop {
|
|
49
|
+
match (ai.next(), bi.next()) {
|
|
50
|
+
(None, None) => return Ordering::Equal,
|
|
51
|
+
(None, Some(_)) => return Ordering::Less,
|
|
52
|
+
(Some(_), None) => return Ordering::Greater,
|
|
53
|
+
(Some(ac), Some(bc)) => match (ac as u32).cmp(&(bc as u32)) {
|
|
54
|
+
Ordering::Equal => {}
|
|
55
|
+
other => return other,
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fn encode_string(value: &str) -> CanonResult<String> {
|
|
62
|
+
let mut out = String::from("\"");
|
|
63
|
+
for ch in value.chars() {
|
|
64
|
+
let cp = ch as u32;
|
|
65
|
+
match ch {
|
|
66
|
+
'"' => out.push_str("\\\""),
|
|
67
|
+
'\\' => out.push_str("\\\\"),
|
|
68
|
+
_ if cp <= 0x1f => out.push_str(&format!("\\u{cp:04x}")),
|
|
69
|
+
_ => out.push(ch),
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
out.push('"');
|
|
73
|
+
Ok(out)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fn encode_value(value: &CanonValue) -> CanonResult<String> {
|
|
77
|
+
match value {
|
|
78
|
+
CanonValue::FloatMarker => Err(CanonError::new(
|
|
79
|
+
"floats are forbidden in canonical content; pre-represent as int or string",
|
|
80
|
+
)),
|
|
81
|
+
CanonValue::NanMarker => Err(CanonError::new("NaN is forbidden in canonical content")),
|
|
82
|
+
CanonValue::InfMarker => Err(CanonError::new("Infinity is forbidden in canonical content")),
|
|
83
|
+
CanonValue::SurrogateMarker => Err(CanonError::new(
|
|
84
|
+
"unpaired surrogate U+D800 is not allowed in canonical content",
|
|
85
|
+
)),
|
|
86
|
+
CanonValue::Null => Ok("null".to_string()),
|
|
87
|
+
CanonValue::Bool(v) => Ok(if *v { "true" } else { "false" }.to_string()),
|
|
88
|
+
CanonValue::Int(v) => Ok(v.to_string()),
|
|
89
|
+
CanonValue::String(v) => encode_string(v),
|
|
90
|
+
CanonValue::Array(items) => {
|
|
91
|
+
let mut parts = Vec::with_capacity(items.len());
|
|
92
|
+
for item in items {
|
|
93
|
+
parts.push(encode_value(item)?);
|
|
94
|
+
}
|
|
95
|
+
Ok(format!("[{}]", parts.join(",")))
|
|
96
|
+
}
|
|
97
|
+
CanonValue::Object(entries) => {
|
|
98
|
+
let mut sorted = entries.clone();
|
|
99
|
+
sorted.sort_by(|(ak, _), (bk, _)| compare_code_points(ak, bk));
|
|
100
|
+
let mut parts = Vec::with_capacity(sorted.len());
|
|
101
|
+
for (key, item) in sorted {
|
|
102
|
+
parts.push(format!("{}:{}", encode_string(&key)?, encode_value(&item)?));
|
|
103
|
+
}
|
|
104
|
+
Ok(format!("{{{}}}", parts.join(",")))
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
pub fn canonical_bytes(value: &CanonValue) -> CanonResult<Vec<u8>> {
|
|
110
|
+
Ok(encode_value(value)?.into_bytes())
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fn strip_top_level_digest(value: &CanonValue) -> CanonValue {
|
|
114
|
+
match value {
|
|
115
|
+
CanonValue::Object(entries) => CanonValue::Object(
|
|
116
|
+
entries
|
|
117
|
+
.iter()
|
|
118
|
+
.filter(|(key, _)| key != "digest")
|
|
119
|
+
.cloned()
|
|
120
|
+
.collect(),
|
|
121
|
+
),
|
|
122
|
+
_ => value.clone(),
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
pub fn digest(value: &CanonValue, profile: &str) -> CanonResult<String> {
|
|
127
|
+
if !PROFILES.contains(&profile) {
|
|
128
|
+
return Err(CanonError::new(format!("unknown digest profile: {profile}")));
|
|
129
|
+
}
|
|
130
|
+
let mut hasher = Sha256::new();
|
|
131
|
+
hasher.update(format!("{DOMAIN_PREFIX}{profile}\n").as_bytes());
|
|
132
|
+
hasher.update(canonical_bytes(&strip_top_level_digest(value))?);
|
|
133
|
+
Ok(hex::encode(hasher.finalize()))
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fn json_truthy(value: &Value) -> bool {
|
|
137
|
+
match value {
|
|
138
|
+
Value::Null => false,
|
|
139
|
+
Value::Bool(v) => *v,
|
|
140
|
+
Value::Number(n) => n.as_i64().map_or(true, |v| v != 0),
|
|
141
|
+
Value::String(s) => !s.is_empty(),
|
|
142
|
+
Value::Array(_) | Value::Object(_) => true,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fn parse_int_payload(payload: &Value) -> CanonResult<BigInt> {
|
|
147
|
+
let raw = payload
|
|
148
|
+
.as_str()
|
|
149
|
+
.ok_or_else(|| CanonError::new("$int payload must be a decimal string"))?;
|
|
150
|
+
raw.parse::<BigInt>()
|
|
151
|
+
.map_err(|_| CanonError::new(format!("invalid integer payload: {raw}")))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
pub fn decode_input(node: &Value) -> CanonResult<CanonValue> {
|
|
155
|
+
match node {
|
|
156
|
+
Value::Null => Ok(CanonValue::Null),
|
|
157
|
+
Value::Bool(v) => Ok(CanonValue::Bool(*v)),
|
|
158
|
+
Value::Number(n) => {
|
|
159
|
+
if let Some(v) = n.as_i64() {
|
|
160
|
+
Ok(CanonValue::Int(BigInt::from(v)))
|
|
161
|
+
} else if let Some(v) = n.as_u64() {
|
|
162
|
+
Ok(CanonValue::Int(BigInt::from(v)))
|
|
163
|
+
} else {
|
|
164
|
+
Ok(CanonValue::FloatMarker)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
Value::String(v) => Ok(CanonValue::String(v.clone())),
|
|
168
|
+
Value::Array(items) => items.iter().map(decode_input).collect::<CanonResult<Vec<_>>>().map(CanonValue::Array),
|
|
169
|
+
Value::Object(map) => {
|
|
170
|
+
if map.len() == 1 {
|
|
171
|
+
let (tag, payload) = map.iter().next().expect("single object entry");
|
|
172
|
+
match tag.as_str() {
|
|
173
|
+
"$int" => return Ok(CanonValue::Int(parse_int_payload(payload)?)),
|
|
174
|
+
"$float" => return Ok(CanonValue::FloatMarker),
|
|
175
|
+
"$nan" => return Ok(CanonValue::NanMarker),
|
|
176
|
+
"$inf" => return Ok(CanonValue::InfMarker),
|
|
177
|
+
"$surrogate" => return Ok(CanonValue::SurrogateMarker),
|
|
178
|
+
"$str" => {
|
|
179
|
+
return payload
|
|
180
|
+
.as_str()
|
|
181
|
+
.map(|s| CanonValue::String(s.to_string()))
|
|
182
|
+
.ok_or_else(|| CanonError::new("$str payload must be a string"));
|
|
183
|
+
}
|
|
184
|
+
"$bool" => return Ok(CanonValue::Bool(json_truthy(payload))),
|
|
185
|
+
"$null" => return Ok(CanonValue::Null),
|
|
186
|
+
"$obj" => {
|
|
187
|
+
let obj = payload
|
|
188
|
+
.as_object()
|
|
189
|
+
.ok_or_else(|| CanonError::new("$obj payload must be an object"))?;
|
|
190
|
+
let mut entries = Vec::with_capacity(obj.len());
|
|
191
|
+
for (key, item) in obj {
|
|
192
|
+
entries.push((key.clone(), decode_input(item)?));
|
|
193
|
+
}
|
|
194
|
+
return Ok(CanonValue::Object(entries));
|
|
195
|
+
}
|
|
196
|
+
"$arr" => {
|
|
197
|
+
let arr = payload
|
|
198
|
+
.as_array()
|
|
199
|
+
.ok_or_else(|| CanonError::new("$arr payload must be an array"))?;
|
|
200
|
+
return arr.iter().map(decode_input).collect::<CanonResult<Vec<_>>>().map(CanonValue::Array);
|
|
201
|
+
}
|
|
202
|
+
_ => {}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
let mut entries = Vec::with_capacity(map.len());
|
|
206
|
+
for (key, item) in map {
|
|
207
|
+
entries.push((key.clone(), decode_input(item)?));
|
|
208
|
+
}
|
|
209
|
+
Ok(CanonValue::Object(entries))
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
pub fn decode_input_json(tagged_json: &str) -> CanonResult<CanonValue> {
|
|
215
|
+
let value: Value = serde_json::from_str(tagged_json)
|
|
216
|
+
.map_err(|error| CanonError::new(format!("invalid JSON: {error}")))?;
|
|
217
|
+
decode_input(&value)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
pub fn parse_vector_corpus(raw: &str) -> CanonResult<Vec<Value>> {
|
|
221
|
+
let rust_parseable = raw.replace("\"$str\": \"\\ud800\"", "\"$surrogate\": \"d800\"");
|
|
222
|
+
serde_json::from_str(&rust_parseable)
|
|
223
|
+
.map_err(|error| CanonError::new(format!("invalid vector corpus: {error}")))
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
pub fn canonical_bytes_from_json(tagged_json: &str) -> CanonResult<Vec<u8>> {
|
|
227
|
+
canonical_bytes(&decode_input_json(tagged_json)?)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
pub fn digest_from_json(tagged_json: &str, profile: &str) -> CanonResult<String> {
|
|
231
|
+
digest(&decode_input_json(tagged_json)?, profile)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#[cfg(feature = "wasm-binding")]
|
|
235
|
+
use wasm_bindgen::prelude::*;
|
|
236
|
+
|
|
237
|
+
#[cfg(feature = "wasm-binding")]
|
|
238
|
+
#[wasm_bindgen(js_name = canonicalBytesFromJson)]
|
|
239
|
+
pub fn wasm_canonical_bytes_from_json(tagged_json: &str) -> Result<Vec<u8>, JsValue> {
|
|
240
|
+
canonical_bytes_from_json(tagged_json).map_err(|error| JsValue::from_str(&error.to_string()))
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
#[cfg(feature = "wasm-binding")]
|
|
244
|
+
#[wasm_bindgen(js_name = digestFromJson)]
|
|
245
|
+
pub fn wasm_digest_from_json(tagged_json: &str, profile: &str) -> Result<String, JsValue> {
|
|
246
|
+
digest_from_json(tagged_json, profile).map_err(|error| JsValue::from_str(&error.to_string()))
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
#[cfg(feature = "pyo3-binding")]
|
|
250
|
+
use pyo3::prelude::*;
|
|
251
|
+
|
|
252
|
+
#[cfg(feature = "pyo3-binding")]
|
|
253
|
+
fn py_error(error: CanonError) -> PyErr {
|
|
254
|
+
pyo3::exceptions::PyValueError::new_err(error.to_string())
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
#[cfg(feature = "pyo3-binding")]
|
|
258
|
+
#[pyfunction(name = "canonical_bytes_from_json")]
|
|
259
|
+
fn py_canonical_bytes_from_json(tagged_json: &str) -> PyResult<Vec<u8>> {
|
|
260
|
+
canonical_bytes_from_json(tagged_json).map_err(py_error)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
#[cfg(feature = "pyo3-binding")]
|
|
264
|
+
#[pyfunction(name = "digest_from_json")]
|
|
265
|
+
fn py_digest_from_json(tagged_json: &str, profile: &str) -> PyResult<String> {
|
|
266
|
+
digest_from_json(tagged_json, profile).map_err(py_error)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
#[cfg(feature = "pyo3-binding")]
|
|
270
|
+
#[pymodule]
|
|
271
|
+
fn canon_core(module: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
272
|
+
module.add_function(wrap_pyfunction!(py_canonical_bytes_from_json, module)?)?;
|
|
273
|
+
module.add_function(wrap_pyfunction!(py_digest_from_json, module)?)?;
|
|
274
|
+
Ok(())
|
|
275
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
use base64::engine::general_purpose::STANDARD;
|
|
2
|
+
use base64::Engine;
|
|
3
|
+
use canon_core::{canonical_bytes, decode_input, digest, parse_vector_corpus};
|
|
4
|
+
use serde_json::Value;
|
|
5
|
+
use std::fs;
|
|
6
|
+
use std::path::PathBuf;
|
|
7
|
+
|
|
8
|
+
fn vectors_path() -> PathBuf {
|
|
9
|
+
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../vectors/canon-vectors.json")
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
#[test]
|
|
13
|
+
fn rust_core_matches_pinned_vectors() {
|
|
14
|
+
let raw = fs::read_to_string(vectors_path()).expect("read canon vectors");
|
|
15
|
+
let vectors: Vec<Value> = parse_vector_corpus(&raw).expect("parse canon vectors");
|
|
16
|
+
assert!(!vectors.is_empty(), "canon vector corpus must be non-empty");
|
|
17
|
+
for vector in vectors {
|
|
18
|
+
let name = vector["name"].as_str().expect("name");
|
|
19
|
+
let value = decode_input(&vector["input"]).unwrap_or_else(|error| panic!("{name}: decode failed: {error}"));
|
|
20
|
+
if vector.get("expect_error").and_then(Value::as_bool).unwrap_or(false) {
|
|
21
|
+
assert!(canonical_bytes(&value).is_err(), "{name}: expected canonical_bytes to reject");
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
let profile = vector["profile"].as_str().expect("profile");
|
|
25
|
+
let bytes = canonical_bytes(&value).unwrap_or_else(|error| panic!("{name}: encode failed: {error}"));
|
|
26
|
+
let bytes_b64 = STANDARD.encode(bytes);
|
|
27
|
+
assert_eq!(
|
|
28
|
+
bytes_b64,
|
|
29
|
+
vector["expected_canonical_bytes_b64"].as_str().expect("expected bytes"),
|
|
30
|
+
"{name}: bytes mismatch",
|
|
31
|
+
);
|
|
32
|
+
let digest_hex = digest(&value, profile).unwrap_or_else(|error| panic!("{name}: digest failed: {error}"));
|
|
33
|
+
assert_eq!(
|
|
34
|
+
digest_hex,
|
|
35
|
+
vector["expected_digest_hex"].as_str().expect("expected digest"),
|
|
36
|
+
"{name}: digest mismatch",
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|