@ternent/seal 0.3.10
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/README.md +258 -0
- package/bin/seal +6 -0
- package/dist/artifact.js +256 -0
- package/dist/artifact.js.map +1 -0
- package/dist/chunks/utils.es-ad8f1dc4.js +54 -0
- package/dist/chunks/utils.es-ad8f1dc4.js.map +1 -0
- package/dist/cli.js +441 -0
- package/dist/cli.js.map +1 -0
- package/dist/crypto.js +41 -0
- package/dist/crypto.js.map +1 -0
- package/dist/errors.js +65 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.js +82 -0
- package/dist/manifest.js.map +1 -0
- package/dist/proof.js +237 -0
- package/dist/proof.js.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# @ternent/seal-cli
|
|
2
|
+
|
|
3
|
+
Seal signs files and manifests. It verifies artifacts offline. It emits portable proof JSON for legacy flows and recipient-targeted sealed artifacts for encrypted flows.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add -D @ternent/seal-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Environment
|
|
12
|
+
|
|
13
|
+
Create a signer identity with Seal:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
seal identity create --out identity.json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Create a mnemonic-backed identity and save the recovery phrase separately:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
seal identity create --out identity.json --words 24 --mnemonic-out seal-seed-phrase.txt
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`@ternent/seal-cli` reads signer material from a v2 identity JSON payload:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
export SEAL_IDENTITY="$(cat identity.json)"
|
|
29
|
+
# or
|
|
30
|
+
export SEAL_IDENTITY_FILE="./identity.json"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Commands
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
seal identity create --out identity.json
|
|
37
|
+
seal identity create --out identity.json --words 24 --mnemonic-out seal-seed-phrase.txt
|
|
38
|
+
seal manifest create --input apps/seal/dist --out apps/seal/dist/dist-manifest.json
|
|
39
|
+
seal sign --input apps/seal/dist/dist-manifest.json --out apps/seal/dist/proof.json
|
|
40
|
+
seal sign --input artifact.tar.gz --recipient age1... --recipient age1... --out artifact.seal.json
|
|
41
|
+
seal verify --proof apps/seal/dist/proof.json --input apps/seal/dist/dist-manifest.json --json
|
|
42
|
+
seal verify --artifact artifact.seal.json --json
|
|
43
|
+
seal public-key --json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
When one or more `--recipient` flags are provided, Seal encrypts the input bytes with `@ternent/armour`, then signs the unsigned artifact container. Recipient values are never serialized into the emitted artifact.
|
|
47
|
+
|
|
48
|
+
## JavaScript API
|
|
49
|
+
|
|
50
|
+
Create a Seal-compatible identity and persist it as the `SEAL_IDENTITY` payload:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import {
|
|
54
|
+
createSealIdentity,
|
|
55
|
+
createSealMnemonicIdentity,
|
|
56
|
+
exportIdentityJson,
|
|
57
|
+
} from "@ternent/seal-cli";
|
|
58
|
+
|
|
59
|
+
const identity = await createSealIdentity();
|
|
60
|
+
const identityJson = exportIdentityJson(identity);
|
|
61
|
+
|
|
62
|
+
const { identity: mnemonicIdentity, mnemonic } = await createSealMnemonicIdentity({ words: 24 });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Create and verify recipient-targeted artifacts:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import {
|
|
69
|
+
createSealArtifact,
|
|
70
|
+
decryptSealArtifactPayload,
|
|
71
|
+
verifySealArtifact,
|
|
72
|
+
} from "@ternent/seal-cli/artifact";
|
|
73
|
+
|
|
74
|
+
const artifact = await createSealArtifact({
|
|
75
|
+
signer: { identity },
|
|
76
|
+
subjectPath: "artifact.tar.gz",
|
|
77
|
+
payload: artifactBytes,
|
|
78
|
+
recipients: ["age1..."],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const verification = await verifySealArtifact(artifact);
|
|
82
|
+
const plaintext = await decryptSealArtifactPayload({
|
|
83
|
+
artifact,
|
|
84
|
+
identity,
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## GitHub Actions
|
|
89
|
+
|
|
90
|
+
Use the published GitHub Action:
|
|
91
|
+
|
|
92
|
+
```yaml
|
|
93
|
+
- name: Generate Seal artifacts
|
|
94
|
+
uses: samternent/seal-action@v2
|
|
95
|
+
env:
|
|
96
|
+
SEAL_IDENTITY: ${{ secrets.SEAL_IDENTITY }}
|
|
97
|
+
with:
|
|
98
|
+
assets-directory: dist
|
|
99
|
+
package-name: @ternent/seal-cli
|
|
100
|
+
package-version: latest
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The action is intentionally narrow. Your workflow still needs to:
|
|
104
|
+
|
|
105
|
+
- check out the repo
|
|
106
|
+
- set up Node
|
|
107
|
+
- build the static directory you want to sign
|
|
108
|
+
|
|
109
|
+
Inputs:
|
|
110
|
+
|
|
111
|
+
- `assets-directory`: built static directory to sign
|
|
112
|
+
- `working-directory`: base directory for path resolution
|
|
113
|
+
- `manifest-name`: manifest output filename
|
|
114
|
+
- `proof-name`: proof output filename
|
|
115
|
+
- `public-key-name`: public key output filename
|
|
116
|
+
- `package-name`: npm package name to execute when `cli-command` is omitted
|
|
117
|
+
- `package-version`: npm version or dist-tag to execute when `cli-command` is omitted
|
|
118
|
+
- `cli-command`: command prefix used to invoke Seal
|
|
119
|
+
|
|
120
|
+
Outputs:
|
|
121
|
+
|
|
122
|
+
- `manifest-path`
|
|
123
|
+
- `proof-path`
|
|
124
|
+
- `public-key-path`
|
|
125
|
+
|
|
126
|
+
The default path is npm-backed: if `cli-command` is empty, the action runs `npm exec --yes --package=<package-name>@<package-version> seal`.
|
|
127
|
+
|
|
128
|
+
## Schemas
|
|
129
|
+
|
|
130
|
+
Manifest:
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"version": "1",
|
|
135
|
+
"type": "seal-manifest",
|
|
136
|
+
"root": "dist",
|
|
137
|
+
"files": {
|
|
138
|
+
"assets/index.js": "sha256:..."
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Proof:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"version": "2",
|
|
148
|
+
"type": "seal-proof",
|
|
149
|
+
"algorithm": "Ed25519",
|
|
150
|
+
"createdAt": "2026-03-13T00:00:00.000Z",
|
|
151
|
+
"subject": {
|
|
152
|
+
"kind": "manifest",
|
|
153
|
+
"path": "dist-manifest.json",
|
|
154
|
+
"hash": "sha256:..."
|
|
155
|
+
},
|
|
156
|
+
"signer": {
|
|
157
|
+
"publicKey": "BASE64URL-RAW-ED25519-PUBLIC-KEY",
|
|
158
|
+
"keyId": "..."
|
|
159
|
+
},
|
|
160
|
+
"signature": "..."
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Encrypted artifact:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"version": "1",
|
|
169
|
+
"type": "seal-artifact",
|
|
170
|
+
"manifest": {
|
|
171
|
+
"version": "1",
|
|
172
|
+
"payloadType": "encrypted",
|
|
173
|
+
"payloadScheme": "age",
|
|
174
|
+
"payloadMode": "recipients",
|
|
175
|
+
"payloadEncoding": "armor",
|
|
176
|
+
"payloadHash": "sha256:..."
|
|
177
|
+
},
|
|
178
|
+
"payload": {
|
|
179
|
+
"type": "encrypted",
|
|
180
|
+
"scheme": "age",
|
|
181
|
+
"mode": "recipients",
|
|
182
|
+
"encoding": "armor",
|
|
183
|
+
"data": "-----BEGIN AGE ENCRYPTED FILE-----\n...\n-----END AGE ENCRYPTED FILE-----\n"
|
|
184
|
+
},
|
|
185
|
+
"proof": {
|
|
186
|
+
"version": "2",
|
|
187
|
+
"type": "seal-proof",
|
|
188
|
+
"algorithm": "Ed25519",
|
|
189
|
+
"createdAt": "2026-03-17T00:00:00.000Z",
|
|
190
|
+
"subject": {
|
|
191
|
+
"kind": "artifact",
|
|
192
|
+
"path": "artifact.tar.gz",
|
|
193
|
+
"hash": "sha256:..."
|
|
194
|
+
},
|
|
195
|
+
"signer": {
|
|
196
|
+
"publicKey": "BASE64URL-RAW-ED25519-PUBLIC-KEY",
|
|
197
|
+
"keyId": "..."
|
|
198
|
+
},
|
|
199
|
+
"signature": "..."
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Public key:
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"version": "2",
|
|
209
|
+
"type": "seal-public-key",
|
|
210
|
+
"algorithm": "Ed25519",
|
|
211
|
+
"publicKey": "BASE64URL-RAW-ED25519-PUBLIC-KEY",
|
|
212
|
+
"keyId": "..."
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Identity:
|
|
217
|
+
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"format": "ternent-identity",
|
|
221
|
+
"version": "2",
|
|
222
|
+
"algorithm": "Ed25519",
|
|
223
|
+
"createdAt": "2026-03-17T00:00:00.000Z",
|
|
224
|
+
"publicKey": "BASE64URL-RAW-ED25519-PUBLIC-KEY",
|
|
225
|
+
"keyId": "...",
|
|
226
|
+
"material": {
|
|
227
|
+
"kind": "seed",
|
|
228
|
+
"seed": "BASE64URL-RAW-32-BYTE-SEED"
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Frontend Contract
|
|
234
|
+
|
|
235
|
+
`apps/seal` verifies published artifacts by fetching:
|
|
236
|
+
|
|
237
|
+
- `/dist-manifest.json`
|
|
238
|
+
- `/proof.json`
|
|
239
|
+
- `/public-key.json` (optional)
|
|
240
|
+
|
|
241
|
+
Browser verification reuses `@ternent/seal-cli/proof`, `@ternent/seal-cli/crypto`, `@ternent/identity`, and `ternent-utils`.
|
|
242
|
+
|
|
243
|
+
Validation rules:
|
|
244
|
+
|
|
245
|
+
- parse `seal-proof`
|
|
246
|
+
- recompute the fetched manifest hash from raw bytes
|
|
247
|
+
- verify the embedded signature
|
|
248
|
+
- verify `signer.keyId`
|
|
249
|
+
- if `/public-key.json` exists, require its `publicKey` and `keyId` to match the proof signer
|
|
250
|
+
|
|
251
|
+
## Exit Codes
|
|
252
|
+
|
|
253
|
+
- `0` success
|
|
254
|
+
- `1` general failure
|
|
255
|
+
- `2` subject hash mismatch
|
|
256
|
+
- `3` signature invalid
|
|
257
|
+
- `4` invalid proof
|
|
258
|
+
- `5` key or config error
|
package/bin/seal
ADDED
package/dist/artifact.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { initArmour, encryptForRecipients, decryptWithIdentity } from "@ternent/armour";
|
|
2
|
+
import { c as canonicalStringify } from "./chunks/utils.es-ad8f1dc4.js";
|
|
3
|
+
import { createSealHash, createSealProof, validateSealProofShape, verifySealProofSignature } from "./proof.js";
|
|
4
|
+
import { toSealEncryptionError, unsupportedEncryptionModeError, toSealDecryptionError } from "./errors.js";
|
|
5
|
+
import "./crypto.js";
|
|
6
|
+
import "@ternent/identity";
|
|
7
|
+
const utf8Encoder = new TextEncoder();
|
|
8
|
+
const utf8Decoder = new TextDecoder();
|
|
9
|
+
const SEAL_ARTIFACT_VERSION = "1";
|
|
10
|
+
const SEAL_ARTIFACT_TYPE = "seal-artifact";
|
|
11
|
+
const SEAL_ARTIFACT_MANIFEST_VERSION = "1";
|
|
12
|
+
function isRecord(value) {
|
|
13
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
function hasOnlyKeys(value, allowed) {
|
|
16
|
+
return Object.keys(value).every((key) => allowed.includes(key));
|
|
17
|
+
}
|
|
18
|
+
function isSealHash(value) {
|
|
19
|
+
return typeof value === "string" && /^sha256:[0-9a-f]{64}$/.test(value);
|
|
20
|
+
}
|
|
21
|
+
function normalizeBytes(value) {
|
|
22
|
+
return value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
23
|
+
}
|
|
24
|
+
function getSealArtifactUnsignedFields(artifact) {
|
|
25
|
+
return {
|
|
26
|
+
version: artifact.version,
|
|
27
|
+
type: artifact.type,
|
|
28
|
+
manifest: artifact.manifest,
|
|
29
|
+
payload: artifact.payload
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function getUnsignedArtifactBytes(artifact) {
|
|
33
|
+
return utf8Encoder.encode(canonicalStringify(getSealArtifactUnsignedFields(artifact)));
|
|
34
|
+
}
|
|
35
|
+
async function createSealArtifact(input) {
|
|
36
|
+
const plaintext = normalizeBytes(input.payload);
|
|
37
|
+
try {
|
|
38
|
+
await initArmour();
|
|
39
|
+
const ciphertext = await encryptForRecipients({
|
|
40
|
+
recipients: input.recipients,
|
|
41
|
+
data: plaintext,
|
|
42
|
+
output: "armor"
|
|
43
|
+
});
|
|
44
|
+
const payloadData = utf8Decoder.decode(ciphertext);
|
|
45
|
+
const manifest = {
|
|
46
|
+
version: SEAL_ARTIFACT_MANIFEST_VERSION,
|
|
47
|
+
payloadType: "encrypted",
|
|
48
|
+
payloadScheme: "age",
|
|
49
|
+
payloadMode: "recipients",
|
|
50
|
+
payloadEncoding: "armor",
|
|
51
|
+
payloadHash: await createSealHash(ciphertext)
|
|
52
|
+
};
|
|
53
|
+
const payload = {
|
|
54
|
+
type: "encrypted",
|
|
55
|
+
scheme: "age",
|
|
56
|
+
mode: "recipients",
|
|
57
|
+
encoding: "armor",
|
|
58
|
+
data: payloadData
|
|
59
|
+
};
|
|
60
|
+
const unsignedArtifact = {
|
|
61
|
+
version: SEAL_ARTIFACT_VERSION,
|
|
62
|
+
type: SEAL_ARTIFACT_TYPE,
|
|
63
|
+
manifest,
|
|
64
|
+
payload
|
|
65
|
+
};
|
|
66
|
+
const proof = await createSealProof({
|
|
67
|
+
createdAt: input.createdAt,
|
|
68
|
+
signer: input.signer,
|
|
69
|
+
subject: {
|
|
70
|
+
kind: "artifact",
|
|
71
|
+
path: input.subjectPath,
|
|
72
|
+
hash: await createSealHash(getUnsignedArtifactBytes(unsignedArtifact))
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
...unsignedArtifact,
|
|
77
|
+
proof
|
|
78
|
+
};
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw toSealEncryptionError(error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function validateSealArtifactShape(value) {
|
|
84
|
+
if (!isRecord(value)) {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
errors: ["Artifact must be a JSON object."],
|
|
88
|
+
artifact: null
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const errors = [];
|
|
92
|
+
if (!hasOnlyKeys(value, ["version", "type", "manifest", "payload", "proof"])) {
|
|
93
|
+
errors.push("Artifact contains unsupported fields.");
|
|
94
|
+
}
|
|
95
|
+
if (value.version !== SEAL_ARTIFACT_VERSION) {
|
|
96
|
+
errors.push(`Artifact version must be ${SEAL_ARTIFACT_VERSION}.`);
|
|
97
|
+
}
|
|
98
|
+
if (value.type !== SEAL_ARTIFACT_TYPE) {
|
|
99
|
+
errors.push(`Artifact type must be ${SEAL_ARTIFACT_TYPE}.`);
|
|
100
|
+
}
|
|
101
|
+
if (!isRecord(value.manifest)) {
|
|
102
|
+
errors.push("Artifact manifest must be an object.");
|
|
103
|
+
}
|
|
104
|
+
if (!isRecord(value.payload)) {
|
|
105
|
+
errors.push("Artifact payload must be an object.");
|
|
106
|
+
}
|
|
107
|
+
if (!isRecord(value.proof)) {
|
|
108
|
+
errors.push("Artifact proof must be an object.");
|
|
109
|
+
}
|
|
110
|
+
if (errors.length > 0 || !isRecord(value.manifest) || !isRecord(value.payload) || !isRecord(value.proof)) {
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
errors,
|
|
114
|
+
artifact: null
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (!hasOnlyKeys(value.manifest, [
|
|
118
|
+
"version",
|
|
119
|
+
"payloadType",
|
|
120
|
+
"payloadScheme",
|
|
121
|
+
"payloadMode",
|
|
122
|
+
"payloadEncoding",
|
|
123
|
+
"payloadHash"
|
|
124
|
+
])) {
|
|
125
|
+
errors.push("Artifact manifest contains unsupported fields.");
|
|
126
|
+
}
|
|
127
|
+
if (!hasOnlyKeys(value.payload, ["type", "scheme", "mode", "encoding", "data"])) {
|
|
128
|
+
errors.push("Artifact payload contains unsupported fields.");
|
|
129
|
+
}
|
|
130
|
+
if (value.manifest.version !== SEAL_ARTIFACT_MANIFEST_VERSION) {
|
|
131
|
+
errors.push(`Artifact manifest version must be ${SEAL_ARTIFACT_MANIFEST_VERSION}.`);
|
|
132
|
+
}
|
|
133
|
+
if (value.manifest.payloadType !== "encrypted") {
|
|
134
|
+
errors.push("Artifact manifest payloadType must be encrypted.");
|
|
135
|
+
}
|
|
136
|
+
if (value.manifest.payloadScheme !== "age") {
|
|
137
|
+
errors.push("Artifact manifest payloadScheme must be age.");
|
|
138
|
+
}
|
|
139
|
+
if (value.manifest.payloadMode !== "recipients") {
|
|
140
|
+
errors.push("Artifact manifest payloadMode must be recipients.");
|
|
141
|
+
}
|
|
142
|
+
if (value.manifest.payloadEncoding !== "armor") {
|
|
143
|
+
errors.push("Artifact manifest payloadEncoding must be armor.");
|
|
144
|
+
}
|
|
145
|
+
if (!isSealHash(value.manifest.payloadHash)) {
|
|
146
|
+
errors.push("Artifact manifest payloadHash must be a sha256 hash.");
|
|
147
|
+
}
|
|
148
|
+
if (value.payload.type !== "encrypted") {
|
|
149
|
+
errors.push("Artifact payload type must be encrypted.");
|
|
150
|
+
}
|
|
151
|
+
if (value.payload.scheme !== "age") {
|
|
152
|
+
errors.push("Artifact payload scheme must be age.");
|
|
153
|
+
}
|
|
154
|
+
if (value.payload.mode !== "recipients") {
|
|
155
|
+
errors.push("Artifact payload mode must be recipients.");
|
|
156
|
+
}
|
|
157
|
+
if (value.payload.encoding !== "armor") {
|
|
158
|
+
errors.push("Artifact payload encoding must be armor.");
|
|
159
|
+
}
|
|
160
|
+
if (typeof value.payload.data !== "string" || value.payload.data.length === 0) {
|
|
161
|
+
errors.push("Artifact payload data must be a non-empty string.");
|
|
162
|
+
}
|
|
163
|
+
if (value.manifest.payloadType !== value.payload.type || value.manifest.payloadScheme !== value.payload.scheme || value.manifest.payloadMode !== value.payload.mode || value.manifest.payloadEncoding !== value.payload.encoding) {
|
|
164
|
+
errors.push("Artifact manifest and payload metadata must match.");
|
|
165
|
+
}
|
|
166
|
+
const proofValidation = validateSealProofShape(value.proof);
|
|
167
|
+
if (!proofValidation.ok || !proofValidation.proof) {
|
|
168
|
+
errors.push(...proofValidation.errors);
|
|
169
|
+
} else if (proofValidation.proof.subject.kind !== "artifact") {
|
|
170
|
+
errors.push("Artifact proof subject kind must be artifact.");
|
|
171
|
+
}
|
|
172
|
+
if (errors.length > 0) {
|
|
173
|
+
return {
|
|
174
|
+
ok: false,
|
|
175
|
+
errors,
|
|
176
|
+
artifact: null
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
ok: true,
|
|
181
|
+
errors: [],
|
|
182
|
+
artifact: value
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function parseSealArtifactJson(raw) {
|
|
186
|
+
try {
|
|
187
|
+
return validateSealArtifactShape(JSON.parse(raw));
|
|
188
|
+
} catch {
|
|
189
|
+
return {
|
|
190
|
+
ok: false,
|
|
191
|
+
errors: ["Artifact JSON is not valid JSON."],
|
|
192
|
+
artifact: null
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async function verifySealArtifact(artifact) {
|
|
197
|
+
const validation = validateSealArtifactShape(artifact);
|
|
198
|
+
if (!validation.ok || !validation.artifact) {
|
|
199
|
+
return {
|
|
200
|
+
valid: false,
|
|
201
|
+
hashMatch: false,
|
|
202
|
+
signatureValid: false,
|
|
203
|
+
encrypted: true,
|
|
204
|
+
payloadScheme: "age",
|
|
205
|
+
payloadMode: "recipients",
|
|
206
|
+
keyId: "",
|
|
207
|
+
algorithm: "Ed25519",
|
|
208
|
+
subjectHash: "sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
|
209
|
+
errors: validation.errors
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const signatureCheck = await verifySealProofSignature(artifact.proof);
|
|
213
|
+
const subjectHash = await createSealHash(getUnsignedArtifactBytes(artifact));
|
|
214
|
+
const payloadHash = await createSealHash(utf8Encoder.encode(artifact.payload.data));
|
|
215
|
+
const artifactHashMatch = artifact.proof.subject.hash === subjectHash;
|
|
216
|
+
const payloadHashMatch = artifact.manifest.payloadHash === payloadHash;
|
|
217
|
+
const errors = [...signatureCheck.errors];
|
|
218
|
+
if (!artifactHashMatch) {
|
|
219
|
+
errors.push("Artifact hash does not match proof subject hash.");
|
|
220
|
+
}
|
|
221
|
+
if (!payloadHashMatch) {
|
|
222
|
+
errors.push("Encrypted payload hash does not match manifest payload hash.");
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
valid: signatureCheck.ok && artifactHashMatch && payloadHashMatch,
|
|
226
|
+
hashMatch: artifactHashMatch && payloadHashMatch,
|
|
227
|
+
signatureValid: signatureCheck.ok,
|
|
228
|
+
encrypted: true,
|
|
229
|
+
payloadScheme: artifact.payload.scheme,
|
|
230
|
+
payloadMode: artifact.payload.mode,
|
|
231
|
+
keyId: artifact.proof.signer.keyId,
|
|
232
|
+
algorithm: artifact.proof.algorithm,
|
|
233
|
+
subjectHash,
|
|
234
|
+
errors
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
async function decryptSealArtifactPayload(input) {
|
|
238
|
+
const verification = await verifySealArtifact(input.artifact);
|
|
239
|
+
if (!verification.valid) {
|
|
240
|
+
throw new Error(verification.errors.join(" ") || "Artifact verification failed.");
|
|
241
|
+
}
|
|
242
|
+
if (input.artifact.payload.type !== "encrypted" || input.artifact.payload.scheme !== "age" || input.artifact.payload.mode !== "recipients" || input.artifact.payload.encoding !== "armor") {
|
|
243
|
+
throw unsupportedEncryptionModeError("Seal only supports age recipient-mode armored payloads.");
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
await initArmour();
|
|
247
|
+
return await decryptWithIdentity({
|
|
248
|
+
identity: input.identity,
|
|
249
|
+
data: utf8Encoder.encode(input.artifact.payload.data)
|
|
250
|
+
});
|
|
251
|
+
} catch (error) {
|
|
252
|
+
throw toSealDecryptionError(error);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
export { SEAL_ARTIFACT_MANIFEST_VERSION, SEAL_ARTIFACT_TYPE, SEAL_ARTIFACT_VERSION, createSealArtifact, decryptSealArtifactPayload, getSealArtifactUnsignedFields, parseSealArtifactJson, validateSealArtifactShape, verifySealArtifact };
|
|
256
|
+
//# sourceMappingURL=artifact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifact.js","sources":["../src/artifact.ts"],"sourcesContent":["import {\n decryptWithIdentity,\n encryptForRecipients,\n initArmour,\n type ArmourIdentityInput,\n} from \"@ternent/armour\";\nimport { canonicalStringify } from \"ternent-utils\";\nimport type { SealHash } from \"./manifest\";\nimport {\n createSealHash,\n createSealProof,\n validateSealProofShape,\n verifySealProofSignature,\n type SealProofV1,\n} from \"./proof\";\nimport type { SealSignerInput } from \"./crypto\";\nimport {\n toSealDecryptionError,\n toSealEncryptionError,\n unsupportedEncryptionModeError,\n} from \"./errors\";\n\nconst utf8Encoder = new TextEncoder();\nconst utf8Decoder = new TextDecoder();\n\nexport const SEAL_ARTIFACT_VERSION = \"1\" as const;\nexport const SEAL_ARTIFACT_TYPE = \"seal-artifact\" as const;\nexport const SEAL_ARTIFACT_MANIFEST_VERSION = \"1\" as const;\n\nexport type SealArtifactManifestV1 = {\n version: typeof SEAL_ARTIFACT_MANIFEST_VERSION;\n payloadType: \"encrypted\";\n payloadScheme: \"age\";\n payloadMode: \"recipients\";\n payloadEncoding: \"armor\";\n payloadHash: SealHash;\n};\n\nexport type SealEncryptedPayloadV1 = {\n type: \"encrypted\";\n scheme: \"age\";\n mode: \"recipients\";\n encoding: \"armor\";\n data: string;\n};\n\nexport type SealArtifactUnsignedV1 = {\n version: typeof SEAL_ARTIFACT_VERSION;\n type: typeof SEAL_ARTIFACT_TYPE;\n manifest: SealArtifactManifestV1;\n payload: SealEncryptedPayloadV1;\n};\n\nexport type SealArtifactV1 = SealArtifactUnsignedV1 & {\n proof: SealProofV1;\n};\n\nexport type VerifySealArtifactResult = {\n valid: boolean;\n hashMatch: boolean;\n signatureValid: boolean;\n encrypted: boolean;\n payloadScheme: \"age\";\n payloadMode: \"recipients\";\n keyId: string;\n algorithm: SealProofV1[\"algorithm\"];\n subjectHash: SealHash;\n errors: string[];\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction hasOnlyKeys(value: Record<string, unknown>, allowed: string[]): boolean {\n return Object.keys(value).every((key) => allowed.includes(key));\n}\n\nfunction isSealHash(value: unknown): value is SealHash {\n return typeof value === \"string\" && /^sha256:[0-9a-f]{64}$/.test(value);\n}\n\nfunction normalizeBytes(value: Uint8Array | ArrayBuffer): Uint8Array {\n return value instanceof Uint8Array ? value : new Uint8Array(value);\n}\n\nexport function getSealArtifactUnsignedFields(\n artifact: SealArtifactV1 | SealArtifactUnsignedV1,\n): SealArtifactUnsignedV1 {\n return {\n version: artifact.version,\n type: artifact.type,\n manifest: artifact.manifest,\n payload: artifact.payload,\n };\n}\n\nfunction getUnsignedArtifactBytes(artifact: SealArtifactV1 | SealArtifactUnsignedV1): Uint8Array {\n return utf8Encoder.encode(canonicalStringify(getSealArtifactUnsignedFields(artifact)));\n}\n\nexport async function createSealArtifact(input: {\n createdAt?: string;\n signer: SealSignerInput;\n subjectPath: string;\n payload: Uint8Array | ArrayBuffer;\n recipients: string[];\n}): Promise<SealArtifactV1> {\n const plaintext = normalizeBytes(input.payload);\n\n try {\n await initArmour();\n\n const ciphertext = await encryptForRecipients({\n recipients: input.recipients,\n data: plaintext,\n output: \"armor\",\n });\n const payloadData = utf8Decoder.decode(ciphertext);\n const manifest: SealArtifactManifestV1 = {\n version: SEAL_ARTIFACT_MANIFEST_VERSION,\n payloadType: \"encrypted\",\n payloadScheme: \"age\",\n payloadMode: \"recipients\",\n payloadEncoding: \"armor\",\n payloadHash: await createSealHash(ciphertext),\n };\n const payload: SealEncryptedPayloadV1 = {\n type: \"encrypted\",\n scheme: \"age\",\n mode: \"recipients\",\n encoding: \"armor\",\n data: payloadData,\n };\n const unsignedArtifact: SealArtifactUnsignedV1 = {\n version: SEAL_ARTIFACT_VERSION,\n type: SEAL_ARTIFACT_TYPE,\n manifest,\n payload,\n };\n\n const proof = await createSealProof({\n createdAt: input.createdAt,\n signer: input.signer,\n subject: {\n kind: \"artifact\",\n path: input.subjectPath,\n hash: await createSealHash(getUnsignedArtifactBytes(unsignedArtifact)),\n },\n });\n\n return {\n ...unsignedArtifact,\n proof,\n };\n } catch (error) {\n throw toSealEncryptionError(error);\n }\n}\n\nexport function validateSealArtifactShape(value: unknown): {\n ok: boolean;\n errors: string[];\n artifact: SealArtifactV1 | null;\n} {\n if (!isRecord(value)) {\n return {\n ok: false,\n errors: [\"Artifact must be a JSON object.\"],\n artifact: null,\n };\n }\n\n const errors: string[] = [];\n\n if (!hasOnlyKeys(value, [\"version\", \"type\", \"manifest\", \"payload\", \"proof\"])) {\n errors.push(\"Artifact contains unsupported fields.\");\n }\n if (value.version !== SEAL_ARTIFACT_VERSION) {\n errors.push(`Artifact version must be ${SEAL_ARTIFACT_VERSION}.`);\n }\n if (value.type !== SEAL_ARTIFACT_TYPE) {\n errors.push(`Artifact type must be ${SEAL_ARTIFACT_TYPE}.`);\n }\n if (!isRecord(value.manifest)) {\n errors.push(\"Artifact manifest must be an object.\");\n }\n if (!isRecord(value.payload)) {\n errors.push(\"Artifact payload must be an object.\");\n }\n if (!isRecord(value.proof)) {\n errors.push(\"Artifact proof must be an object.\");\n }\n\n if (\n errors.length > 0 ||\n !isRecord(value.manifest) ||\n !isRecord(value.payload) ||\n !isRecord(value.proof)\n ) {\n return {\n ok: false,\n errors,\n artifact: null,\n };\n }\n\n if (\n !hasOnlyKeys(value.manifest, [\n \"version\",\n \"payloadType\",\n \"payloadScheme\",\n \"payloadMode\",\n \"payloadEncoding\",\n \"payloadHash\",\n ])\n ) {\n errors.push(\"Artifact manifest contains unsupported fields.\");\n }\n if (!hasOnlyKeys(value.payload, [\"type\", \"scheme\", \"mode\", \"encoding\", \"data\"])) {\n errors.push(\"Artifact payload contains unsupported fields.\");\n }\n\n if (value.manifest.version !== SEAL_ARTIFACT_MANIFEST_VERSION) {\n errors.push(`Artifact manifest version must be ${SEAL_ARTIFACT_MANIFEST_VERSION}.`);\n }\n if (value.manifest.payloadType !== \"encrypted\") {\n errors.push(\"Artifact manifest payloadType must be encrypted.\");\n }\n if (value.manifest.payloadScheme !== \"age\") {\n errors.push(\"Artifact manifest payloadScheme must be age.\");\n }\n if (value.manifest.payloadMode !== \"recipients\") {\n errors.push(\"Artifact manifest payloadMode must be recipients.\");\n }\n if (value.manifest.payloadEncoding !== \"armor\") {\n errors.push(\"Artifact manifest payloadEncoding must be armor.\");\n }\n if (!isSealHash(value.manifest.payloadHash)) {\n errors.push(\"Artifact manifest payloadHash must be a sha256 hash.\");\n }\n\n if (value.payload.type !== \"encrypted\") {\n errors.push(\"Artifact payload type must be encrypted.\");\n }\n if (value.payload.scheme !== \"age\") {\n errors.push(\"Artifact payload scheme must be age.\");\n }\n if (value.payload.mode !== \"recipients\") {\n errors.push(\"Artifact payload mode must be recipients.\");\n }\n if (value.payload.encoding !== \"armor\") {\n errors.push(\"Artifact payload encoding must be armor.\");\n }\n if (typeof value.payload.data !== \"string\" || value.payload.data.length === 0) {\n errors.push(\"Artifact payload data must be a non-empty string.\");\n }\n\n if (\n value.manifest.payloadType !== value.payload.type ||\n value.manifest.payloadScheme !== value.payload.scheme ||\n value.manifest.payloadMode !== value.payload.mode ||\n value.manifest.payloadEncoding !== value.payload.encoding\n ) {\n errors.push(\"Artifact manifest and payload metadata must match.\");\n }\n\n const proofValidation = validateSealProofShape(value.proof);\n if (!proofValidation.ok || !proofValidation.proof) {\n errors.push(...proofValidation.errors);\n } else if (proofValidation.proof.subject.kind !== \"artifact\") {\n errors.push(\"Artifact proof subject kind must be artifact.\");\n }\n\n if (errors.length > 0) {\n return {\n ok: false,\n errors,\n artifact: null,\n };\n }\n\n return {\n ok: true,\n errors: [],\n artifact: value as SealArtifactV1,\n };\n}\n\nexport function parseSealArtifactJson(raw: string): {\n ok: boolean;\n errors: string[];\n artifact: SealArtifactV1 | null;\n} {\n try {\n return validateSealArtifactShape(JSON.parse(raw));\n } catch {\n return {\n ok: false,\n errors: [\"Artifact JSON is not valid JSON.\"],\n artifact: null,\n };\n }\n}\n\nexport async function verifySealArtifact(\n artifact: SealArtifactV1,\n): Promise<VerifySealArtifactResult> {\n const validation = validateSealArtifactShape(artifact);\n if (!validation.ok || !validation.artifact) {\n return {\n valid: false,\n hashMatch: false,\n signatureValid: false,\n encrypted: true,\n payloadScheme: \"age\",\n payloadMode: \"recipients\",\n keyId: \"\",\n algorithm: \"Ed25519\",\n subjectHash: \"sha256:0000000000000000000000000000000000000000000000000000000000000000\",\n errors: validation.errors,\n };\n }\n\n const signatureCheck = await verifySealProofSignature(artifact.proof);\n const subjectHash = await createSealHash(getUnsignedArtifactBytes(artifact));\n const payloadHash = await createSealHash(utf8Encoder.encode(artifact.payload.data));\n const artifactHashMatch = artifact.proof.subject.hash === subjectHash;\n const payloadHashMatch = artifact.manifest.payloadHash === payloadHash;\n const errors = [...signatureCheck.errors];\n\n if (!artifactHashMatch) {\n errors.push(\"Artifact hash does not match proof subject hash.\");\n }\n if (!payloadHashMatch) {\n errors.push(\"Encrypted payload hash does not match manifest payload hash.\");\n }\n\n return {\n valid: signatureCheck.ok && artifactHashMatch && payloadHashMatch,\n hashMatch: artifactHashMatch && payloadHashMatch,\n signatureValid: signatureCheck.ok,\n encrypted: true,\n payloadScheme: artifact.payload.scheme,\n payloadMode: artifact.payload.mode,\n keyId: artifact.proof.signer.keyId,\n algorithm: artifact.proof.algorithm,\n subjectHash,\n errors,\n };\n}\n\nexport async function decryptSealArtifactPayload(input: {\n artifact: SealArtifactV1;\n identity: ArmourIdentityInput;\n}): Promise<Uint8Array> {\n const verification = await verifySealArtifact(input.artifact);\n if (!verification.valid) {\n throw new Error(verification.errors.join(\" \") || \"Artifact verification failed.\");\n }\n\n if (\n input.artifact.payload.type !== \"encrypted\" ||\n input.artifact.payload.scheme !== \"age\" ||\n input.artifact.payload.mode !== \"recipients\" ||\n input.artifact.payload.encoding !== \"armor\"\n ) {\n throw unsupportedEncryptionModeError(\"Seal only supports age recipient-mode armored payloads.\");\n }\n\n try {\n await initArmour();\n return await decryptWithIdentity({\n identity: input.identity,\n data: utf8Encoder.encode(input.artifact.payload.data),\n });\n } catch (error) {\n throw toSealDecryptionError(error);\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
function toCryptoBuffer(data) {
|
|
2
|
+
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
3
|
+
return Uint8Array.from(bytes).buffer;
|
|
4
|
+
}
|
|
5
|
+
function getHashArray(hash) {
|
|
6
|
+
return Array.from(new Uint8Array(hash));
|
|
7
|
+
}
|
|
8
|
+
function getHashHex(hash) {
|
|
9
|
+
return hash.map((buf) => buf.toString(16).padStart(2, "0")).join("");
|
|
10
|
+
}
|
|
11
|
+
function canonicalize(value, seen) {
|
|
12
|
+
if (value === void 0) {
|
|
13
|
+
throw new TypeError("Cannot hash undefined");
|
|
14
|
+
}
|
|
15
|
+
const valueType = typeof value;
|
|
16
|
+
if (valueType === "function" || valueType === "symbol") {
|
|
17
|
+
throw new TypeError(`Cannot hash ${valueType} values`);
|
|
18
|
+
}
|
|
19
|
+
if (value === null || valueType === "string" || valueType === "number" || valueType === "boolean") {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
if (valueType === "bigint") {
|
|
23
|
+
throw new TypeError("Cannot hash bigint values");
|
|
24
|
+
}
|
|
25
|
+
if (typeof value.toJSON === "function") {
|
|
26
|
+
return canonicalize(value.toJSON(), seen);
|
|
27
|
+
}
|
|
28
|
+
if (Array.isArray(value)) {
|
|
29
|
+
return value.map((item) => canonicalize(item, seen));
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === "object") {
|
|
32
|
+
if (seen.has(value)) {
|
|
33
|
+
throw new TypeError("Cannot hash circular references");
|
|
34
|
+
}
|
|
35
|
+
seen.add(value);
|
|
36
|
+
const entries = Object.keys(value).sort();
|
|
37
|
+
const result = {};
|
|
38
|
+
for (const key of entries) {
|
|
39
|
+
result[key] = canonicalize(value[key], seen);
|
|
40
|
+
}
|
|
41
|
+
seen.delete(value);
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
throw new TypeError(`Cannot hash unsupported value type: ${valueType}`);
|
|
45
|
+
}
|
|
46
|
+
function canonicalStringify(data) {
|
|
47
|
+
return JSON.stringify(canonicalize(data, /* @__PURE__ */ new WeakSet()));
|
|
48
|
+
}
|
|
49
|
+
async function hashBytes(input) {
|
|
50
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", toCryptoBuffer(input));
|
|
51
|
+
return getHashHex(getHashArray(hashBuffer));
|
|
52
|
+
}
|
|
53
|
+
export { canonicalStringify as c, hashBytes as h };
|
|
54
|
+
//# sourceMappingURL=utils.es-ad8f1dc4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.es-ad8f1dc4.js","sources":["../../../utils/dist/utils.es.js"],"sourcesContent":["function getBufferCtor() {\n const maybeBuffer = globalThis.Buffer;\n return maybeBuffer != null ? maybeBuffer : null;\n}\nfunction addNewLines(str) {\n let finalString = \"\";\n while (str.length > 0) {\n finalString += str.substring(0, 64) + \"\\n\";\n str = str.substring(64);\n }\n return finalString;\n}\nfunction removeLines(str) {\n return str.replaceAll(\"\\n\", \"\");\n}\nfunction stripIdentityKey(key) {\n return key.replace(\"-----BEGIN PUBLIC KEY-----\\n\", \"\").replace(\"\\n-----END PUBLIC KEY-----\", \"\");\n}\nfunction formatIdentityKey(key) {\n return `-----BEGIN PUBLIC KEY-----\n${key}\n-----END PUBLIC KEY-----`;\n}\nfunction stripEncryptionFile(file) {\n return file.replace(\"-----BEGIN AGE ENCRYPTED FILE-----\\n\", \"\").replace(\"\\n-----END AGE ENCRYPTED FILE-----\\n\", \"\");\n}\nfunction formatEncryptionFile(file) {\n return `-----BEGIN AGE ENCRYPTED FILE-----\n${file}\n-----END AGE ENCRYPTED FILE-----\n`;\n}\nfunction generateId() {\n const uint32 = crypto.getRandomValues(new Uint32Array(1))[0];\n return uint32.toString(16);\n}\nfunction arrayBufferToBase64(arrayBuffer) {\n const byteArray = new Uint8Array(arrayBuffer);\n const bufferCtor = getBufferCtor();\n if (bufferCtor) {\n return bufferCtor.from(byteArray).toString(\"base64\");\n }\n let byteString = \"\";\n for (let i = 0; i < byteArray.byteLength; i++) {\n byteString += String.fromCharCode(byteArray[i]);\n }\n return btoa(byteString);\n}\nfunction base64ToArrayBuffer(b64) {\n const bufferCtor = getBufferCtor();\n if (bufferCtor) {\n const bytes = Uint8Array.from(\n bufferCtor.from(b64, \"base64\").toString(\"binary\"),\n (char) => char.charCodeAt(0)\n );\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);\n }\n const byteString = atob(b64);\n const byteArray = new Uint8Array(byteString.length);\n for (let i = 0; i < byteString.length; i++) {\n byteArray[i] = byteString.charCodeAt(i);\n }\n return byteArray.buffer;\n}\nfunction b64encode(buf) {\n return arrayBufferToBase64(buf);\n}\nfunction b64decode(str) {\n return base64ToArrayBuffer(str);\n}\nfunction encode(data) {\n const payload = typeof data === \"string\" ? data : JSON.stringify(data);\n return new TextEncoder().encode(payload);\n}\nfunction decode(data) {\n return new TextDecoder(\"utf-8\").decode(new Uint8Array(data));\n}\nfunction toCryptoBuffer(data) {\n const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);\n return Uint8Array.from(bytes).buffer;\n}\nfunction getHashBuffer(data) {\n return crypto.subtle.digest(\"SHA-256\", toCryptoBuffer(encode(data)));\n}\nfunction getHashArray(hash) {\n return Array.from(new Uint8Array(hash));\n}\nfunction getHashHex(hash) {\n return hash.map((buf) => buf.toString(16).padStart(2, \"0\")).join(\"\");\n}\nfunction canonicalize(value, seen) {\n if (value === void 0) {\n throw new TypeError(\"Cannot hash undefined\");\n }\n const valueType = typeof value;\n if (valueType === \"function\" || valueType === \"symbol\") {\n throw new TypeError(`Cannot hash ${valueType} values`);\n }\n if (value === null || valueType === \"string\" || valueType === \"number\" || valueType === \"boolean\") {\n return value;\n }\n if (valueType === \"bigint\") {\n throw new TypeError(\"Cannot hash bigint values\");\n }\n if (typeof value.toJSON === \"function\") {\n return canonicalize(value.toJSON(), seen);\n }\n if (Array.isArray(value)) {\n return value.map((item) => canonicalize(item, seen));\n }\n if (typeof value === \"object\") {\n if (seen.has(value)) {\n throw new TypeError(\"Cannot hash circular references\");\n }\n seen.add(value);\n const entries = Object.keys(value).sort();\n const result = {};\n for (const key of entries) {\n result[key] = canonicalize(value[key], seen);\n }\n seen.delete(value);\n return result;\n }\n throw new TypeError(`Cannot hash unsupported value type: ${valueType}`);\n}\nfunction canonicalStringify(data) {\n return JSON.stringify(canonicalize(data, /* @__PURE__ */ new WeakSet()));\n}\nasync function hashData(data) {\n const hash_buffer = await getHashBuffer(canonicalStringify(data));\n const hash_array = getHashArray(hash_buffer);\n return getHashHex(hash_array);\n}\nasync function hashBytes(input) {\n const hashBuffer = await crypto.subtle.digest(\"SHA-256\", toCryptoBuffer(input));\n return getHashHex(getHashArray(hashBuffer));\n}\nfunction generateColorStops(primaryColor, secondaryColor, steps) {\n const colors = [];\n for (let i = 0; i <= steps; i++) {\n const ratio = i / steps;\n const color = lerpColor(primaryColor, secondaryColor, ratio);\n colors.push(color);\n }\n return colors;\n}\nfunction lerpColor(color1, color2, ratio) {\n const r1 = parseInt(color1.substring(1, 3), 16);\n const g1 = parseInt(color1.substring(3, 5), 16);\n const b1 = parseInt(color1.substring(5, 7), 16);\n const r2 = parseInt(color2.substring(1, 3), 16);\n const g2 = parseInt(color2.substring(3, 5), 16);\n const b2 = parseInt(color2.substring(5, 7), 16);\n const r = Math.round(r1 + (r2 - r1) * ratio);\n const g = Math.round(g1 + (g2 - g1) * ratio);\n const b = Math.round(b1 + (b2 - b1) * ratio);\n return `#${(1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1).toUpperCase()}`;\n}\nexport { addNewLines, arrayBufferToBase64, b64decode, b64encode, base64ToArrayBuffer, canonicalStringify, decode, encode, formatEncryptionFile, formatIdentityKey, generateColorStops, generateId, getHashArray, getHashBuffer, getHashHex, hashBytes, hashData, removeLines, stripEncryptionFile, stripIdentityKey };\n"],"names":[],"mappings":"AA6EA,SAAS,eAAe,MAAM;AAC5B,QAAM,QAAQ,gBAAgB,aAAa,OAAO,IAAI,WAAW,IAAI;AACrE,SAAO,WAAW,KAAK,KAAK,EAAE;AAChC;AAIA,SAAS,aAAa,MAAM;AAC1B,SAAO,MAAM,KAAK,IAAI,WAAW,IAAI,CAAC;AACxC;AACA,SAAS,WAAW,MAAM;AACxB,SAAO,KAAK,IAAI,CAAC,QAAQ,IAAI,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AACrE;AACA,SAAS,aAAa,OAAO,MAAM;AACjC,MAAI,UAAU,QAAQ;AACpB,UAAM,IAAI,UAAU,uBAAuB;AAAA,EAC5C;AACD,QAAM,YAAY,OAAO;AACzB,MAAI,cAAc,cAAc,cAAc,UAAU;AACtD,UAAM,IAAI,UAAU,eAAe,kBAAkB;AAAA,EACtD;AACD,MAAI,UAAU,QAAQ,cAAc,YAAY,cAAc,YAAY,cAAc,WAAW;AACjG,WAAO;AAAA,EACR;AACD,MAAI,cAAc,UAAU;AAC1B,UAAM,IAAI,UAAU,2BAA2B;AAAA,EAChD;AACD,MAAI,OAAO,MAAM,WAAW,YAAY;AACtC,WAAO,aAAa,MAAM,OAAQ,GAAE,IAAI;AAAA,EACzC;AACD,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,IAAI,CAAC,SAAS,aAAa,MAAM,IAAI,CAAC;AAAA,EACpD;AACD,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI,KAAK,IAAI,KAAK,GAAG;AACnB,YAAM,IAAI,UAAU,iCAAiC;AAAA,IACtD;AACD,SAAK,IAAI,KAAK;AACd,UAAM,UAAU,OAAO,KAAK,KAAK,EAAE,KAAI;AACvC,UAAM,SAAS,CAAA;AACf,eAAW,OAAO,SAAS;AACzB,aAAO,OAAO,aAAa,MAAM,MAAM,IAAI;AAAA,IAC5C;AACD,SAAK,OAAO,KAAK;AACjB,WAAO;AAAA,EACR;AACD,QAAM,IAAI,UAAU,uCAAuC,WAAW;AACxE;AACA,SAAS,mBAAmB,MAAM;AAChC,SAAO,KAAK,UAAU,aAAa,MAAsB,oBAAI,QAAS,CAAA,CAAC;AACzE;AAMA,eAAe,UAAU,OAAO;AAC9B,QAAM,aAAa,MAAM,OAAO,OAAO,OAAO,WAAW,eAAe,KAAK,CAAC;AAC9E,SAAO,WAAW,aAAa,UAAU,CAAC;AAC5C;;"}
|