@totemsdk/raster-proof 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +383 -0
- package/dist/canonical.d.ts +40 -0
- package/dist/canonical.js +76 -0
- package/dist/hash.d.ts +19 -0
- package/dist/hash.js +32 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +72 -0
- package/dist/manifest.d.ts +24 -0
- package/dist/manifest.js +146 -0
- package/dist/merkle.d.ts +49 -0
- package/dist/merkle.js +171 -0
- package/dist/proof.d.ts +54 -0
- package/dist/proof.js +247 -0
- package/dist/proofgraph.d.ts +51 -0
- package/dist/proofgraph.js +120 -0
- package/dist/provenance.d.ts +29 -0
- package/dist/provenance.js +95 -0
- package/dist/spatial.d.ts +23 -0
- package/dist/spatial.js +77 -0
- package/dist/types.d.ts +189 -0
- package/dist/types.js +14 -0
- package/dist/window.d.ts +19 -0
- package/dist/window.js +59 -0
- package/package.json +69 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module @totemsdk/raster-proof
|
|
4
|
+
*
|
|
5
|
+
* Edge-capable raster and visual evidence proof primitives for Totem Edge.
|
|
6
|
+
*
|
|
7
|
+
* This package proves bytes, manifests, provenance, windows and declared
|
|
8
|
+
* relationships. It does NOT prove that a visual interpretation is correct
|
|
9
|
+
* unless a reviewer or downstream model proof says so.
|
|
10
|
+
*
|
|
11
|
+
* Pipeline:
|
|
12
|
+
*
|
|
13
|
+
* bytes + asset metadata + spatial context + provenance
|
|
14
|
+
* → chunk hashes / Merkle root
|
|
15
|
+
* → deterministic raster manifest
|
|
16
|
+
* → window proof (optional)
|
|
17
|
+
* → Totem proof envelope
|
|
18
|
+
* → proofgraph linkage
|
|
19
|
+
*
|
|
20
|
+
* This is NOT a raster-processing engine: no GDAL, no GeoTIFF parsing, no
|
|
21
|
+
* satellite provider APIs, no STAC, no cloud masking, no NDVI, no ML
|
|
22
|
+
* segmentation, no orthomosaic generation, no tile server, no storage.
|
|
23
|
+
*
|
|
24
|
+
* No network, no storage, no map rendering, no GIS engine dependency.
|
|
25
|
+
* SHA3-256 everywhere, safe to run on edge devices.
|
|
26
|
+
*/
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.addRasterManifestToGraph = exports.rasterWindowProofToGraphEdges = exports.rasterManifestToGraphEdges = exports.rasterWindowProofToProofGraphNode = exports.rasterManifestToProofGraphNode = exports.verifyRasterProof = exports.signRasterProof = exports.createUnsignedRasterProof = exports.rasterEvidenceRefs = exports.createRasterSpatialRelation = exports.rasterFootprintToSpatialObject = exports.verifyRasterDerivation = exports.createDerivedRasterManifest = exports.rasterWindowProofToEvidenceRef = exports.createRasterWindowProof = exports.rasterManifestToEvidenceRef = exports.validateRasterManifest = exports.createRasterManifest = exports.createRasterMerkleSummary = exports.verifyMerkleProof = exports.createMerkleProof = exports.merkleLeafHash = exports.computeMerkleRoot = exports.chunkBytes = exports.DEFAULT_CHUNK_SIZE_BYTES = exports.hashSubarray = exports.hashString = exports.hashBytes = exports.hashRasterWindowProof = exports.computeRasterWindowProofId = exports.hashRasterManifest = exports.computeRasterManifestId = exports.toHex = exports.canonicalJson = void 0;
|
|
29
|
+
var canonical_js_1 = require("./canonical.js");
|
|
30
|
+
Object.defineProperty(exports, "canonicalJson", { enumerable: true, get: function () { return canonical_js_1.canonicalJson; } });
|
|
31
|
+
Object.defineProperty(exports, "toHex", { enumerable: true, get: function () { return canonical_js_1.toHex; } });
|
|
32
|
+
var canonical_js_2 = require("./canonical.js");
|
|
33
|
+
Object.defineProperty(exports, "computeRasterManifestId", { enumerable: true, get: function () { return canonical_js_2.computeRasterManifestId; } });
|
|
34
|
+
Object.defineProperty(exports, "hashRasterManifest", { enumerable: true, get: function () { return canonical_js_2.hashRasterManifest; } });
|
|
35
|
+
Object.defineProperty(exports, "computeRasterWindowProofId", { enumerable: true, get: function () { return canonical_js_2.computeRasterWindowProofId; } });
|
|
36
|
+
Object.defineProperty(exports, "hashRasterWindowProof", { enumerable: true, get: function () { return canonical_js_2.hashRasterWindowProof; } });
|
|
37
|
+
var hash_js_1 = require("./hash.js");
|
|
38
|
+
Object.defineProperty(exports, "hashBytes", { enumerable: true, get: function () { return hash_js_1.hashBytes; } });
|
|
39
|
+
Object.defineProperty(exports, "hashString", { enumerable: true, get: function () { return hash_js_1.hashString; } });
|
|
40
|
+
Object.defineProperty(exports, "hashSubarray", { enumerable: true, get: function () { return hash_js_1.hashSubarray; } });
|
|
41
|
+
var merkle_js_1 = require("./merkle.js");
|
|
42
|
+
Object.defineProperty(exports, "DEFAULT_CHUNK_SIZE_BYTES", { enumerable: true, get: function () { return merkle_js_1.DEFAULT_CHUNK_SIZE_BYTES; } });
|
|
43
|
+
Object.defineProperty(exports, "chunkBytes", { enumerable: true, get: function () { return merkle_js_1.chunkBytes; } });
|
|
44
|
+
Object.defineProperty(exports, "computeMerkleRoot", { enumerable: true, get: function () { return merkle_js_1.computeMerkleRoot; } });
|
|
45
|
+
Object.defineProperty(exports, "merkleLeafHash", { enumerable: true, get: function () { return merkle_js_1.merkleLeafHash; } });
|
|
46
|
+
Object.defineProperty(exports, "createMerkleProof", { enumerable: true, get: function () { return merkle_js_1.createMerkleProof; } });
|
|
47
|
+
Object.defineProperty(exports, "verifyMerkleProof", { enumerable: true, get: function () { return merkle_js_1.verifyMerkleProof; } });
|
|
48
|
+
Object.defineProperty(exports, "createRasterMerkleSummary", { enumerable: true, get: function () { return merkle_js_1.createRasterMerkleSummary; } });
|
|
49
|
+
var manifest_js_1 = require("./manifest.js");
|
|
50
|
+
Object.defineProperty(exports, "createRasterManifest", { enumerable: true, get: function () { return manifest_js_1.createRasterManifest; } });
|
|
51
|
+
Object.defineProperty(exports, "validateRasterManifest", { enumerable: true, get: function () { return manifest_js_1.validateRasterManifest; } });
|
|
52
|
+
Object.defineProperty(exports, "rasterManifestToEvidenceRef", { enumerable: true, get: function () { return manifest_js_1.rasterManifestToEvidenceRef; } });
|
|
53
|
+
var window_js_1 = require("./window.js");
|
|
54
|
+
Object.defineProperty(exports, "createRasterWindowProof", { enumerable: true, get: function () { return window_js_1.createRasterWindowProof; } });
|
|
55
|
+
Object.defineProperty(exports, "rasterWindowProofToEvidenceRef", { enumerable: true, get: function () { return window_js_1.rasterWindowProofToEvidenceRef; } });
|
|
56
|
+
var provenance_js_1 = require("./provenance.js");
|
|
57
|
+
Object.defineProperty(exports, "createDerivedRasterManifest", { enumerable: true, get: function () { return provenance_js_1.createDerivedRasterManifest; } });
|
|
58
|
+
Object.defineProperty(exports, "verifyRasterDerivation", { enumerable: true, get: function () { return provenance_js_1.verifyRasterDerivation; } });
|
|
59
|
+
var spatial_js_1 = require("./spatial.js");
|
|
60
|
+
Object.defineProperty(exports, "rasterFootprintToSpatialObject", { enumerable: true, get: function () { return spatial_js_1.rasterFootprintToSpatialObject; } });
|
|
61
|
+
Object.defineProperty(exports, "createRasterSpatialRelation", { enumerable: true, get: function () { return spatial_js_1.createRasterSpatialRelation; } });
|
|
62
|
+
var proof_js_1 = require("./proof.js");
|
|
63
|
+
Object.defineProperty(exports, "rasterEvidenceRefs", { enumerable: true, get: function () { return proof_js_1.rasterEvidenceRefs; } });
|
|
64
|
+
Object.defineProperty(exports, "createUnsignedRasterProof", { enumerable: true, get: function () { return proof_js_1.createUnsignedRasterProof; } });
|
|
65
|
+
Object.defineProperty(exports, "signRasterProof", { enumerable: true, get: function () { return proof_js_1.signRasterProof; } });
|
|
66
|
+
Object.defineProperty(exports, "verifyRasterProof", { enumerable: true, get: function () { return proof_js_1.verifyRasterProof; } });
|
|
67
|
+
var proofgraph_js_1 = require("./proofgraph.js");
|
|
68
|
+
Object.defineProperty(exports, "rasterManifestToProofGraphNode", { enumerable: true, get: function () { return proofgraph_js_1.rasterManifestToProofGraphNode; } });
|
|
69
|
+
Object.defineProperty(exports, "rasterWindowProofToProofGraphNode", { enumerable: true, get: function () { return proofgraph_js_1.rasterWindowProofToProofGraphNode; } });
|
|
70
|
+
Object.defineProperty(exports, "rasterManifestToGraphEdges", { enumerable: true, get: function () { return proofgraph_js_1.rasterManifestToGraphEdges; } });
|
|
71
|
+
Object.defineProperty(exports, "rasterWindowProofToGraphEdges", { enumerable: true, get: function () { return proofgraph_js_1.rasterWindowProofToGraphEdges; } });
|
|
72
|
+
Object.defineProperty(exports, "addRasterManifestToGraph", { enumerable: true, get: function () { return proofgraph_js_1.addRasterManifestToGraph; } });
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raster manifest construction and validation for @totemsdk/raster-proof.
|
|
3
|
+
*
|
|
4
|
+
* A RasterManifest is the deterministic, content-addressed record of "what
|
|
5
|
+
* this raster is, where it came from, what bytes back it, and what spatial
|
|
6
|
+
* context it has". Creation is pure — no I/O, no format sniffing.
|
|
7
|
+
*/
|
|
8
|
+
import type { EvidenceRef } from '@totemsdk/proof';
|
|
9
|
+
import type { CreateRasterManifestParams, RasterManifest, RasterValidationResult } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Create a deterministic RasterManifest. rasterId is computed from stable
|
|
12
|
+
* fields (everything except rasterId and metadata).
|
|
13
|
+
*/
|
|
14
|
+
export declare function createRasterManifest(params: CreateRasterManifestParams): RasterManifest;
|
|
15
|
+
/**
|
|
16
|
+
* Validate the structure of a RasterManifest. Structural only — does not
|
|
17
|
+
* re-hash bytes (verification of a proof envelope also recomputes the ID and
|
|
18
|
+
* manifest hash).
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateRasterManifest(manifest: RasterManifest): RasterValidationResult;
|
|
21
|
+
/**
|
|
22
|
+
* Convert a RasterManifest into an EvidenceRef for inclusion in a proof.
|
|
23
|
+
*/
|
|
24
|
+
export declare function rasterManifestToEvidenceRef(manifest: RasterManifest): EvidenceRef;
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Raster manifest construction and validation for @totemsdk/raster-proof.
|
|
4
|
+
*
|
|
5
|
+
* A RasterManifest is the deterministic, content-addressed record of "what
|
|
6
|
+
* this raster is, where it came from, what bytes back it, and what spatial
|
|
7
|
+
* context it has". Creation is pure — no I/O, no format sniffing.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.createRasterManifest = createRasterManifest;
|
|
11
|
+
exports.validateRasterManifest = validateRasterManifest;
|
|
12
|
+
exports.rasterManifestToEvidenceRef = rasterManifestToEvidenceRef;
|
|
13
|
+
const canonical_js_1 = require("./canonical.js");
|
|
14
|
+
const CAPTURE_SKEW_MS = 24 * 60 * 60 * 1000; // capturedAt may lag createdAt by ≤ 24h
|
|
15
|
+
function inLonRange(v) {
|
|
16
|
+
return Number.isFinite(v) && v >= -180 && v <= 180;
|
|
17
|
+
}
|
|
18
|
+
function inLatRange(v) {
|
|
19
|
+
return Number.isFinite(v) && v >= -90 && v <= 90;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create a deterministic RasterManifest. rasterId is computed from stable
|
|
23
|
+
* fields (everything except rasterId and metadata).
|
|
24
|
+
*/
|
|
25
|
+
function createRasterManifest(params) {
|
|
26
|
+
const createdAt = params.createdAt ?? Date.now();
|
|
27
|
+
const body = {
|
|
28
|
+
sourceType: params.sourceType,
|
|
29
|
+
layerType: params.layerType,
|
|
30
|
+
...(params.capturedAt !== undefined ? { capturedAt: params.capturedAt } : {}),
|
|
31
|
+
createdAt,
|
|
32
|
+
...(params.deviceId !== undefined ? { deviceId: params.deviceId } : {}),
|
|
33
|
+
...(params.operatorId !== undefined ? { operatorId: params.operatorId } : {}),
|
|
34
|
+
...(params.missionId !== undefined ? { missionId: params.missionId } : {}),
|
|
35
|
+
...(params.providerId !== undefined ? { providerId: params.providerId } : {}),
|
|
36
|
+
...(params.sceneId !== undefined ? { sceneId: params.sceneId } : {}),
|
|
37
|
+
asset: params.asset,
|
|
38
|
+
...(params.spatial !== undefined ? { spatial: params.spatial } : {}),
|
|
39
|
+
...(params.provenance !== undefined ? { provenance: params.provenance } : {}),
|
|
40
|
+
...(params.metadata !== undefined ? { metadata: params.metadata } : {}),
|
|
41
|
+
};
|
|
42
|
+
return { ...body, rasterId: (0, canonical_js_1.computeRasterManifestId)(body) };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Validate the structure of a RasterManifest. Structural only — does not
|
|
46
|
+
* re-hash bytes (verification of a proof envelope also recomputes the ID and
|
|
47
|
+
* manifest hash).
|
|
48
|
+
*/
|
|
49
|
+
function validateRasterManifest(manifest) {
|
|
50
|
+
const errors = [];
|
|
51
|
+
const warnings = [];
|
|
52
|
+
if (typeof manifest.rasterId !== 'string' || manifest.rasterId.length === 0) {
|
|
53
|
+
errors.push('rasterId is required');
|
|
54
|
+
}
|
|
55
|
+
if (!manifest.sourceType) {
|
|
56
|
+
errors.push('sourceType is required');
|
|
57
|
+
}
|
|
58
|
+
if (!manifest.layerType) {
|
|
59
|
+
errors.push('layerType is required');
|
|
60
|
+
}
|
|
61
|
+
if (!manifest.asset || typeof manifest.asset !== 'object') {
|
|
62
|
+
errors.push('asset is required');
|
|
63
|
+
}
|
|
64
|
+
if (manifest.asset && !manifest.asset.format) {
|
|
65
|
+
errors.push('asset.format is required');
|
|
66
|
+
}
|
|
67
|
+
if (manifest.asset && (typeof manifest.asset.contentHash !== 'string' || manifest.asset.contentHash.length === 0)) {
|
|
68
|
+
errors.push('asset.contentHash is required');
|
|
69
|
+
}
|
|
70
|
+
if (manifest.asset && manifest.asset.hashAlgorithm !== 'sha3-256') {
|
|
71
|
+
errors.push('asset.hashAlgorithm must be sha3-256');
|
|
72
|
+
}
|
|
73
|
+
if (manifest.asset && manifest.asset.byteSize !== undefined && manifest.asset.byteSize < 0) {
|
|
74
|
+
errors.push('asset.byteSize must be non-negative');
|
|
75
|
+
}
|
|
76
|
+
if (manifest.asset && manifest.asset.merkleRoot !== undefined && !/^[a-f0-9]{64}$/.test(manifest.asset.merkleRoot)) {
|
|
77
|
+
errors.push('asset.merkleRoot must be 64-char lowercase hex (sha3-256)');
|
|
78
|
+
}
|
|
79
|
+
if (manifest.asset && manifest.asset.chunkSizeBytes !== undefined && manifest.asset.chunkSizeBytes <= 0) {
|
|
80
|
+
errors.push('asset.chunkSizeBytes must be positive');
|
|
81
|
+
}
|
|
82
|
+
if (!Number.isFinite(manifest.createdAt) || manifest.createdAt <= 0) {
|
|
83
|
+
errors.push('createdAt must be finite and positive');
|
|
84
|
+
}
|
|
85
|
+
if (manifest.capturedAt !== undefined) {
|
|
86
|
+
if (!Number.isFinite(manifest.capturedAt) || manifest.capturedAt <= 0) {
|
|
87
|
+
errors.push('capturedAt must be finite and positive');
|
|
88
|
+
}
|
|
89
|
+
else if (manifest.capturedAt > manifest.createdAt + CAPTURE_SKEW_MS) {
|
|
90
|
+
const allowed = manifest.metadata?.['allowFutureCapture'] === true;
|
|
91
|
+
if (!allowed) {
|
|
92
|
+
errors.push('capturedAt is far after createdAt; set metadata.allowFutureCapture = true to allow');
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
warnings.push('capturedAt is far after createdAt (explicitly allowed by metadata.allowFutureCapture)');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (manifest.spatial) {
|
|
100
|
+
if (manifest.spatial.bounds !== undefined) {
|
|
101
|
+
const b = manifest.spatial.bounds;
|
|
102
|
+
if (b.length !== 4) {
|
|
103
|
+
errors.push('spatial.bounds must be [minLon, minLat, maxLon, maxLat]');
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
const [minLon, minLat, maxLon, maxLat] = b;
|
|
107
|
+
if (!inLonRange(minLon) || !inLonRange(maxLon)) {
|
|
108
|
+
errors.push('spatial.bounds longitudes must be in [-180, 180]');
|
|
109
|
+
}
|
|
110
|
+
if (!inLatRange(minLat) || !inLatRange(maxLat)) {
|
|
111
|
+
errors.push('spatial.bounds latitudes must be in [-90, 90]');
|
|
112
|
+
}
|
|
113
|
+
if (minLon > maxLon)
|
|
114
|
+
errors.push('spatial.bounds minLon must be <= maxLon');
|
|
115
|
+
if (minLat > maxLat)
|
|
116
|
+
errors.push('spatial.bounds minLat must be <= maxLat');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (manifest.spatial.widthPx !== undefined && (!Number.isInteger(manifest.spatial.widthPx) || manifest.spatial.widthPx <= 0)) {
|
|
120
|
+
errors.push('spatial.widthPx must be a positive integer');
|
|
121
|
+
}
|
|
122
|
+
if (manifest.spatial.heightPx !== undefined && (!Number.isInteger(manifest.spatial.heightPx) || manifest.spatial.heightPx <= 0)) {
|
|
123
|
+
errors.push('spatial.heightPx must be a positive integer');
|
|
124
|
+
}
|
|
125
|
+
if (manifest.spatial.resolutionM !== undefined && (!Number.isFinite(manifest.spatial.resolutionM) || manifest.spatial.resolutionM <= 0)) {
|
|
126
|
+
errors.push('spatial.resolutionM must be positive');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (manifest.sourceType === 'derived') {
|
|
130
|
+
const derivedFrom = manifest.provenance?.derivedFrom ?? [];
|
|
131
|
+
if (derivedFrom.length === 0) {
|
|
132
|
+
warnings.push('derived raster should declare provenance.derivedFrom source raster IDs');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Convert a RasterManifest into an EvidenceRef for inclusion in a proof.
|
|
139
|
+
*/
|
|
140
|
+
function rasterManifestToEvidenceRef(manifest) {
|
|
141
|
+
return {
|
|
142
|
+
id: manifest.rasterId,
|
|
143
|
+
kind: 'raster-manifest',
|
|
144
|
+
hash: (0, canonical_js_1.hashRasterManifest)(manifest),
|
|
145
|
+
};
|
|
146
|
+
}
|
package/dist/merkle.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge-safe Merkle chunking and proof for @totemsdk/raster-proof.
|
|
3
|
+
*
|
|
4
|
+
* DOMAIN SEPARATION: chunk content hashes are SHA3-256 of the raw bytes;
|
|
5
|
+
* Merkle leaves and internal nodes are domain-separated so a chunk hash can
|
|
6
|
+
* never be confused with a node hash:
|
|
7
|
+
* leaf = sha3_256("totem-raster-leaf" + chunk.hash)
|
|
8
|
+
* node = sha3_256("totem-raster-node" + left + right)
|
|
9
|
+
*
|
|
10
|
+
* ODD-LAYER RULE (deterministic): when a Merkle level has an odd number of
|
|
11
|
+
* hashes, the last hash is PROMOTED unchanged to the next level (it is NOT
|
|
12
|
+
* duplicated/hashed with itself). This is the standard RFC 6962-style rule
|
|
13
|
+
* and keeps the tree compact and deterministic.
|
|
14
|
+
*
|
|
15
|
+
* Empty byte arrays are rejected — a raster asset must have bytes.
|
|
16
|
+
*/
|
|
17
|
+
import type { RasterChunk, RasterMerkleOptions, RasterMerkleProof, RasterMerkleSummary } from './types.js';
|
|
18
|
+
export declare const DEFAULT_CHUNK_SIZE_BYTES: number;
|
|
19
|
+
/**
|
|
20
|
+
* Domain-separated Merkle leaf hash for a chunk. A user proving "this chunk
|
|
21
|
+
* is in this tree" recomputes merkleLeafHash(chunk) and compares it to
|
|
22
|
+
* RasterMerkleProof.leafHash before verifying the sibling chain.
|
|
23
|
+
*/
|
|
24
|
+
export declare function merkleLeafHash(chunk: RasterChunk): string;
|
|
25
|
+
/**
|
|
26
|
+
* Split bytes into fixed-size chunks (default 64 KiB). Each chunk carries a
|
|
27
|
+
* content hash of its raw bytes. Empty input is rejected.
|
|
28
|
+
*/
|
|
29
|
+
export declare function chunkBytes(bytes: Uint8Array, chunkSizeBytes?: number): RasterChunk[];
|
|
30
|
+
/**
|
|
31
|
+
* Compute the Merkle root over chunk hashes (deterministic). Odd layers
|
|
32
|
+
* promote the last hash unchanged.
|
|
33
|
+
*/
|
|
34
|
+
export declare function computeMerkleRoot(chunks: RasterChunk[]): string;
|
|
35
|
+
/**
|
|
36
|
+
* Build a Merkle inclusion proof for one chunk. The proof's leafHash is the
|
|
37
|
+
* domain-separated leaf hash of that chunk. Callers can reproduce it with
|
|
38
|
+
* merkleLeafHash(chunks[leafIndex]).
|
|
39
|
+
*/
|
|
40
|
+
export declare function createMerkleProof(chunks: RasterChunk[], leafIndex: number): RasterMerkleProof;
|
|
41
|
+
/**
|
|
42
|
+
* Verify a Merkle inclusion proof against its own root. Structural check —
|
|
43
|
+
* recomputes the root from leafHash + siblings and compares.
|
|
44
|
+
*/
|
|
45
|
+
export declare function verifyMerkleProof(proof: RasterMerkleProof): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Hash bytes, chunk them, and summarize in one edge-safe pass.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createRasterMerkleSummary(bytes: Uint8Array, options?: RasterMerkleOptions): RasterMerkleSummary;
|
package/dist/merkle.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Edge-safe Merkle chunking and proof for @totemsdk/raster-proof.
|
|
4
|
+
*
|
|
5
|
+
* DOMAIN SEPARATION: chunk content hashes are SHA3-256 of the raw bytes;
|
|
6
|
+
* Merkle leaves and internal nodes are domain-separated so a chunk hash can
|
|
7
|
+
* never be confused with a node hash:
|
|
8
|
+
* leaf = sha3_256("totem-raster-leaf" + chunk.hash)
|
|
9
|
+
* node = sha3_256("totem-raster-node" + left + right)
|
|
10
|
+
*
|
|
11
|
+
* ODD-LAYER RULE (deterministic): when a Merkle level has an odd number of
|
|
12
|
+
* hashes, the last hash is PROMOTED unchanged to the next level (it is NOT
|
|
13
|
+
* duplicated/hashed with itself). This is the standard RFC 6962-style rule
|
|
14
|
+
* and keeps the tree compact and deterministic.
|
|
15
|
+
*
|
|
16
|
+
* Empty byte arrays are rejected — a raster asset must have bytes.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.DEFAULT_CHUNK_SIZE_BYTES = void 0;
|
|
20
|
+
exports.merkleLeafHash = merkleLeafHash;
|
|
21
|
+
exports.chunkBytes = chunkBytes;
|
|
22
|
+
exports.computeMerkleRoot = computeMerkleRoot;
|
|
23
|
+
exports.createMerkleProof = createMerkleProof;
|
|
24
|
+
exports.verifyMerkleProof = verifyMerkleProof;
|
|
25
|
+
exports.createRasterMerkleSummary = createRasterMerkleSummary;
|
|
26
|
+
const core_1 = require("@totemsdk/core");
|
|
27
|
+
const canonical_js_1 = require("./canonical.js");
|
|
28
|
+
const hash_js_1 = require("./hash.js");
|
|
29
|
+
const LEAF_PREFIX = 'totem-raster-leaf';
|
|
30
|
+
const NODE_PREFIX = 'totem-raster-node';
|
|
31
|
+
exports.DEFAULT_CHUNK_SIZE_BYTES = 64 * 1024; // 64 KiB
|
|
32
|
+
function domainHash(prefix, a, b) {
|
|
33
|
+
const encoder = new TextEncoder();
|
|
34
|
+
const payload = b === undefined ? prefix + a : prefix + a + b;
|
|
35
|
+
return (0, canonical_js_1.toHex)((0, core_1.sha3_256)(encoder.encode(payload)));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Domain-separated Merkle leaf hash for a chunk. A user proving "this chunk
|
|
39
|
+
* is in this tree" recomputes merkleLeafHash(chunk) and compares it to
|
|
40
|
+
* RasterMerkleProof.leafHash before verifying the sibling chain.
|
|
41
|
+
*/
|
|
42
|
+
function merkleLeafHash(chunk) {
|
|
43
|
+
return domainHash(LEAF_PREFIX, chunk.hash);
|
|
44
|
+
}
|
|
45
|
+
function merkleNodeHash(left, right) {
|
|
46
|
+
return domainHash(NODE_PREFIX, left, right);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Split bytes into fixed-size chunks (default 64 KiB). Each chunk carries a
|
|
50
|
+
* content hash of its raw bytes. Empty input is rejected.
|
|
51
|
+
*/
|
|
52
|
+
function chunkBytes(bytes, chunkSizeBytes = exports.DEFAULT_CHUNK_SIZE_BYTES) {
|
|
53
|
+
if (bytes.length === 0) {
|
|
54
|
+
throw new Error('cannot chunk empty bytes; a raster asset must have content');
|
|
55
|
+
}
|
|
56
|
+
if (!Number.isInteger(chunkSizeBytes) || chunkSizeBytes <= 0) {
|
|
57
|
+
throw new Error('chunk size must be a positive integer');
|
|
58
|
+
}
|
|
59
|
+
const chunks = [];
|
|
60
|
+
for (let offset = 0; offset < bytes.length; offset += chunkSizeBytes) {
|
|
61
|
+
const length = Math.min(chunkSizeBytes, bytes.length - offset);
|
|
62
|
+
chunks.push({
|
|
63
|
+
index: chunks.length,
|
|
64
|
+
offset,
|
|
65
|
+
length,
|
|
66
|
+
hash: (0, hash_js_1.hashBytes)(bytes.subarray(offset, offset + length)),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return chunks;
|
|
70
|
+
}
|
|
71
|
+
function buildLevels(leafHashes) {
|
|
72
|
+
const levels = [leafHashes];
|
|
73
|
+
while (levels[levels.length - 1].length > 1) {
|
|
74
|
+
const cur = levels[levels.length - 1];
|
|
75
|
+
const next = [];
|
|
76
|
+
for (let i = 0; i < cur.length; i += 2) {
|
|
77
|
+
if (i + 1 < cur.length) {
|
|
78
|
+
next.push(merkleNodeHash(cur[i], cur[i + 1]));
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
next.push(cur[i]); // promote the odd last hash unchanged
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
levels.push(next);
|
|
85
|
+
}
|
|
86
|
+
return levels;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Compute the Merkle root over chunk hashes (deterministic). Odd layers
|
|
90
|
+
* promote the last hash unchanged.
|
|
91
|
+
*/
|
|
92
|
+
function computeMerkleRoot(chunks) {
|
|
93
|
+
if (chunks.length === 0) {
|
|
94
|
+
throw new Error('cannot compute a Merkle root over zero chunks');
|
|
95
|
+
}
|
|
96
|
+
const levels = buildLevels(chunks.map((c) => merkleLeafHash(c)));
|
|
97
|
+
return levels[levels.length - 1][0];
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Build a Merkle inclusion proof for one chunk. The proof's leafHash is the
|
|
101
|
+
* domain-separated leaf hash of that chunk. Callers can reproduce it with
|
|
102
|
+
* merkleLeafHash(chunks[leafIndex]).
|
|
103
|
+
*/
|
|
104
|
+
function createMerkleProof(chunks, leafIndex) {
|
|
105
|
+
if (chunks.length === 0) {
|
|
106
|
+
throw new Error('cannot create a Merkle proof over zero chunks');
|
|
107
|
+
}
|
|
108
|
+
if (!Number.isInteger(leafIndex) || leafIndex < 0 || leafIndex >= chunks.length) {
|
|
109
|
+
throw new Error(`leafIndex ${leafIndex} out of range [0, ${chunks.length})`);
|
|
110
|
+
}
|
|
111
|
+
const levels = buildLevels(chunks.map((c) => merkleLeafHash(c)));
|
|
112
|
+
const siblings = [];
|
|
113
|
+
let idx = leafIndex;
|
|
114
|
+
for (let level = 0; level < levels.length - 1; level++) {
|
|
115
|
+
const cur = levels[level];
|
|
116
|
+
const odd = cur.length % 2 === 1;
|
|
117
|
+
if (odd && idx === cur.length - 1) {
|
|
118
|
+
// Last element of an odd level is promoted unchanged — no sibling here,
|
|
119
|
+
// the node itself moves up one level.
|
|
120
|
+
idx = Math.floor(cur.length / 2);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const siblingIndex = idx % 2 === 0 ? idx + 1 : idx - 1;
|
|
124
|
+
siblings.push({
|
|
125
|
+
position: idx % 2 === 0 ? 'right' : 'left',
|
|
126
|
+
hash: cur[siblingIndex],
|
|
127
|
+
});
|
|
128
|
+
idx = Math.floor(idx / 2);
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
root: levels[levels.length - 1][0],
|
|
132
|
+
leafHash: merkleLeafHash(chunks[leafIndex]),
|
|
133
|
+
leafIndex,
|
|
134
|
+
siblings,
|
|
135
|
+
hashAlgorithm: 'sha3-256',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Verify a Merkle inclusion proof against its own root. Structural check —
|
|
140
|
+
* recomputes the root from leafHash + siblings and compares.
|
|
141
|
+
*/
|
|
142
|
+
function verifyMerkleProof(proof) {
|
|
143
|
+
if (proof.hashAlgorithm !== 'sha3-256')
|
|
144
|
+
return false;
|
|
145
|
+
if (!proof.root || !proof.leafHash || proof.leafIndex < 0)
|
|
146
|
+
return false;
|
|
147
|
+
if (!/^[a-f0-9]{64}$/.test(proof.root) || !/^[a-f0-9]{64}$/.test(proof.leafHash))
|
|
148
|
+
return false;
|
|
149
|
+
let h = proof.leafHash;
|
|
150
|
+
for (const sibling of proof.siblings) {
|
|
151
|
+
if (!/^[a-f0-9]{64}$/.test(sibling.hash))
|
|
152
|
+
return false;
|
|
153
|
+
h = sibling.position === 'left' ? merkleNodeHash(sibling.hash, h) : merkleNodeHash(h, sibling.hash);
|
|
154
|
+
}
|
|
155
|
+
return h === proof.root;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Hash bytes, chunk them, and summarize in one edge-safe pass.
|
|
159
|
+
*/
|
|
160
|
+
function createRasterMerkleSummary(bytes, options = {}) {
|
|
161
|
+
const chunkSizeBytes = options.chunkSizeBytes ?? exports.DEFAULT_CHUNK_SIZE_BYTES;
|
|
162
|
+
const contentHash = (0, hash_js_1.hashBytes)(bytes);
|
|
163
|
+
const chunks = chunkBytes(bytes, chunkSizeBytes);
|
|
164
|
+
return {
|
|
165
|
+
contentHash,
|
|
166
|
+
merkleRoot: computeMerkleRoot(chunks),
|
|
167
|
+
chunkSizeBytes,
|
|
168
|
+
chunkCount: chunks.length,
|
|
169
|
+
byteSize: bytes.length,
|
|
170
|
+
};
|
|
171
|
+
}
|
package/dist/proof.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof envelope integration for @totemsdk/raster-proof.
|
|
3
|
+
*
|
|
4
|
+
* Bridges raster manifests, window proofs and Merkle proofs into
|
|
5
|
+
* @totemsdk/proof evidence refs and signed proof envelopes. Signing and
|
|
6
|
+
* verification delegate to @totemsdk/proof. Anchoring is deliberately NOT
|
|
7
|
+
* required for verification.
|
|
8
|
+
*/
|
|
9
|
+
import type { EvidenceRef, SignedProof, UnsignedProof } from '@totemsdk/proof';
|
|
10
|
+
import type { CreateRasterProofParams, RasterManifest, RasterProofVerifyResult, RasterWindowProof } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Build the evidence ref list for a raster proof:
|
|
13
|
+
* - raster manifest hash
|
|
14
|
+
* - content hash
|
|
15
|
+
* - Merkle root when present
|
|
16
|
+
* - window proof hash when present
|
|
17
|
+
* - source raster IDs when derived
|
|
18
|
+
* - spatial object ID when present
|
|
19
|
+
*/
|
|
20
|
+
export declare function rasterEvidenceRefs(manifest: RasterManifest, windowProof?: RasterWindowProof, spatialObjectId?: string): EvidenceRef[];
|
|
21
|
+
/**
|
|
22
|
+
* Create an unsigned attestation proof for a raster manifest.
|
|
23
|
+
*
|
|
24
|
+
* The proof claims: "this manifest describes this asset, produced by this
|
|
25
|
+
* source, at this time, with this content hash / Merkle root". It does NOT
|
|
26
|
+
* claim the visual interpretation is correct — interpretation is an
|
|
27
|
+
* operator / model / reviewer claim made elsewhere.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createUnsignedRasterProof(params: CreateRasterProofParams): UnsignedProof;
|
|
30
|
+
/**
|
|
31
|
+
* Sign an unsigned raster proof with a WOTS key.
|
|
32
|
+
*
|
|
33
|
+
* The caller is responsible for reserving the WOTS key index (see
|
|
34
|
+
* @totemsdk/wots-lease) before calling — one-time key warning applies.
|
|
35
|
+
*/
|
|
36
|
+
export declare function signRasterProof(unsigned: UnsignedProof, seed: Uint8Array, keyIndex: number): SignedProof;
|
|
37
|
+
/**
|
|
38
|
+
* Verify a signed raster proof end to end.
|
|
39
|
+
*
|
|
40
|
+
* Checks:
|
|
41
|
+
* 1. the underlying @totemsdk/proof verification (signature, proofId, expiry)
|
|
42
|
+
* 2. payload contains a structurally valid RasterManifest
|
|
43
|
+
* 3. the manifest rasterId matches a recomputation from its fields
|
|
44
|
+
* 4. the manifest evidence hash matches the payload manifest
|
|
45
|
+
* 5. content hash and Merkle root evidence refs are present when declared
|
|
46
|
+
* 6. window proof (when supplied) has a recomputable ID, matches the
|
|
47
|
+
* manifest's Merkle root, and any supplied Merkle proofs verify
|
|
48
|
+
* 7. provenance structure is valid for derived rasters
|
|
49
|
+
*
|
|
50
|
+
* Anchoring is not required. Source raster manifests are not supplied inside
|
|
51
|
+
* the proof, so derivation checks are structural only (full cross-source
|
|
52
|
+
* verification is @totemsdk/raster-proof's verifyRasterDerivation).
|
|
53
|
+
*/
|
|
54
|
+
export declare function verifyRasterProof(signed: SignedProof): RasterProofVerifyResult;
|