@keetanetwork/anchor 0.0.64 → 0.0.65
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/lib/anchor-external.d.ts +236 -0
- package/lib/anchor-external.d.ts.map +1 -0
- package/lib/anchor-external.generated.d.ts +3 -0
- package/lib/anchor-external.generated.d.ts.map +1 -0
- package/lib/anchor-external.generated.js +209 -0
- package/lib/anchor-external.generated.js.map +1 -0
- package/lib/anchor-external.js +506 -0
- package/lib/anchor-external.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/utils/buffer.d.ts +4 -0
- package/lib/utils/buffer.d.ts.map +1 -1
- package/lib/utils/buffer.js +17 -0
- package/lib/utils/buffer.js.map +1 -1
- package/lib/utils/signing.d.ts +5 -0
- package/lib/utils/signing.d.ts.map +1 -1
- package/lib/utils/signing.js +1 -1
- package/lib/utils/signing.js.map +1 -1
- package/npm-shrinkwrap.json +5 -5
- package/package.json +1 -1
- package/services/asset-movement/common.js +1 -1
- package/services/asset-movement/common.js.map +1 -1
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import { assertEncodedAnchorExternalEnvelopeV1 } from './anchor-external.generated.js';
|
|
2
|
+
import { EncryptedContainer, EncryptedContainerError } from './encrypted-container.js';
|
|
3
|
+
import { KeetaAnchorError, KeetaAnchorUserError, KeetaAnchorUserValidationError } from './error.js';
|
|
4
|
+
import { canonicalizeJson } from './utils/signing.js';
|
|
5
|
+
import { Buffer, arrayBufferToBuffer, decodeBase64Strict } from './utils/buffer.js';
|
|
6
|
+
/**
|
|
7
|
+
* Upper bound on inflated container plaintext, before JSON parsing.
|
|
8
|
+
*
|
|
9
|
+
* Prevents an attacker from forcing a large allocation via a
|
|
10
|
+
* high-ratio gzip payload.
|
|
11
|
+
*/
|
|
12
|
+
const MAX_PLAINTEXT_BYTES = 4096;
|
|
13
|
+
/**
|
|
14
|
+
* Current envelope format version.
|
|
15
|
+
*/
|
|
16
|
+
export const ANCHOR_EXTERNAL_VERSION = 1;
|
|
17
|
+
// #endregion
|
|
18
|
+
// #region Error Handling
|
|
19
|
+
/**
|
|
20
|
+
* Error codes for anchor-external envelope operations.
|
|
21
|
+
*/
|
|
22
|
+
export const AnchorExternalErrorCodes = [
|
|
23
|
+
/*
|
|
24
|
+
* Parsing
|
|
25
|
+
*/
|
|
26
|
+
'BAD_BASE64',
|
|
27
|
+
'NOT_AN_ENVELOPE',
|
|
28
|
+
'UNSUPPORTED_VERSION',
|
|
29
|
+
'NON_CANONICAL',
|
|
30
|
+
'PLAINTEXT_TOO_LARGE',
|
|
31
|
+
/*
|
|
32
|
+
* Non-repudiation
|
|
33
|
+
*/
|
|
34
|
+
'BAD_SIGNATURE',
|
|
35
|
+
'SIGNER_NOT_LISTED',
|
|
36
|
+
'MISSING_BINDING',
|
|
37
|
+
'UNEXPECTED_BINDING',
|
|
38
|
+
/*
|
|
39
|
+
* Confidentiality
|
|
40
|
+
*/
|
|
41
|
+
'EXPECTED_PLAIN',
|
|
42
|
+
'EXPECTED_ENCRYPTED',
|
|
43
|
+
/*
|
|
44
|
+
* Caller-declared budget
|
|
45
|
+
*/
|
|
46
|
+
'OUTPUT_TOO_LARGE'
|
|
47
|
+
];
|
|
48
|
+
const anchorExternalErrorCodeSet = new Set(AnchorExternalErrorCodes);
|
|
49
|
+
/**
|
|
50
|
+
* Error raised by anchor-external decode failures.
|
|
51
|
+
*/
|
|
52
|
+
export class AnchorExternalError extends KeetaAnchorUserError {
|
|
53
|
+
static name = 'AnchorExternalError';
|
|
54
|
+
anchorExternalErrorObjectTypeID;
|
|
55
|
+
static anchorExternalErrorObjectTypeID = '2db22831-216b-4b3e-952a-5f9860f0790b';
|
|
56
|
+
code;
|
|
57
|
+
/**
|
|
58
|
+
* Type-narrow an arbitrary string to {@link AnchorExternalErrorCode}.
|
|
59
|
+
*/
|
|
60
|
+
static isValidCode(code) {
|
|
61
|
+
return (anchorExternalErrorCodeSet.has(code));
|
|
62
|
+
}
|
|
63
|
+
constructor(code, message) {
|
|
64
|
+
super(message);
|
|
65
|
+
this.code = code;
|
|
66
|
+
Object.defineProperty(this, 'anchorExternalErrorObjectTypeID', {
|
|
67
|
+
value: AnchorExternalError.anchorExternalErrorObjectTypeID,
|
|
68
|
+
enumerable: false
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
static isInstance(input) {
|
|
72
|
+
return (this.hasPropWithValue(input, 'anchorExternalErrorObjectTypeID', AnchorExternalError.anchorExternalErrorObjectTypeID));
|
|
73
|
+
}
|
|
74
|
+
toJSON() {
|
|
75
|
+
return ({
|
|
76
|
+
...super.toJSON(),
|
|
77
|
+
code: this.code
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
static async fromJSON(input) {
|
|
81
|
+
const { message, other } = this.extractErrorProperties(input, this);
|
|
82
|
+
if (!('code' in other) || typeof other.code !== 'string') {
|
|
83
|
+
throw (new Error('Invalid AnchorExternalError JSON: missing code property'));
|
|
84
|
+
}
|
|
85
|
+
const code = other.code;
|
|
86
|
+
if (!this.isValidCode(code)) {
|
|
87
|
+
throw (new Error(`Invalid AnchorExternalError JSON: unknown code ${code}`));
|
|
88
|
+
}
|
|
89
|
+
const error = new this(code, message);
|
|
90
|
+
error.restoreFromJSON(other);
|
|
91
|
+
return (error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// #endregion
|
|
95
|
+
// #region Encoded <-> Public mappers
|
|
96
|
+
/**
|
|
97
|
+
* Convert a public entry to an encoded entry.
|
|
98
|
+
*/
|
|
99
|
+
function entryToEncoded(entry) {
|
|
100
|
+
if ('transactionId' in entry) {
|
|
101
|
+
return ({ t: entry.transactionId });
|
|
102
|
+
}
|
|
103
|
+
if ('persistentForwardingId' in entry) {
|
|
104
|
+
return ({ p: entry.persistentForwardingId });
|
|
105
|
+
}
|
|
106
|
+
return ({ d: entry.destination });
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Convert an encoded entry to a public entry.
|
|
110
|
+
*/
|
|
111
|
+
function entryFromEncoded(entry) {
|
|
112
|
+
if ('t' in entry) {
|
|
113
|
+
return ({ transactionId: entry.t });
|
|
114
|
+
}
|
|
115
|
+
if ('p' in entry) {
|
|
116
|
+
return ({ persistentForwardingId: entry.p });
|
|
117
|
+
}
|
|
118
|
+
return ({ destination: entry.d });
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Convert a public envelope to an encoded envelope.
|
|
122
|
+
*/
|
|
123
|
+
function envelopeToEncoded(envelope) {
|
|
124
|
+
const encodedAnchors = {};
|
|
125
|
+
for (const [pk, entry] of Object.entries(envelope.anchors)) {
|
|
126
|
+
encodedAnchors[pk] = entryToEncoded(entry);
|
|
127
|
+
}
|
|
128
|
+
const result = {
|
|
129
|
+
v: ANCHOR_EXTERNAL_VERSION,
|
|
130
|
+
a: encodedAnchors
|
|
131
|
+
};
|
|
132
|
+
if (envelope.binding !== undefined) {
|
|
133
|
+
result.b = { p: envelope.binding.previousBlockHash, o: envelope.binding.operationIndex };
|
|
134
|
+
}
|
|
135
|
+
return (result);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Convert an encoded envelope to a public envelope.
|
|
139
|
+
*/
|
|
140
|
+
function envelopeFromEncoded(encoded) {
|
|
141
|
+
const anchors = {};
|
|
142
|
+
for (const [pk, entry] of Object.entries(encoded.a)) {
|
|
143
|
+
anchors[pk] = entryFromEncoded(entry);
|
|
144
|
+
}
|
|
145
|
+
const result = {
|
|
146
|
+
version: ANCHOR_EXTERNAL_VERSION,
|
|
147
|
+
anchors
|
|
148
|
+
};
|
|
149
|
+
if (encoded.b !== undefined) {
|
|
150
|
+
result.binding = { previousBlockHash: encoded.b.p, operationIndex: encoded.b.o };
|
|
151
|
+
}
|
|
152
|
+
return (result);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Parse an encoded envelope.
|
|
156
|
+
*/
|
|
157
|
+
function parseEncodedEnvelope(value) {
|
|
158
|
+
try {
|
|
159
|
+
return (assertEncodedAnchorExternalEnvelopeV1(value));
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (KeetaAnchorUserValidationError.isTypeGuardErrorLike(error)) {
|
|
163
|
+
if (error.path === '$input.v') {
|
|
164
|
+
throw (new AnchorExternalError('UNSUPPORTED_VERSION', `Unsupported envelope version at ${error.path}: ${String(error.value)}`));
|
|
165
|
+
}
|
|
166
|
+
throw (new AnchorExternalError('NOT_AN_ENVELOPE', `Envelope failed shape check at ${error.path ?? '$input'}: expected ${error.expected}`));
|
|
167
|
+
}
|
|
168
|
+
throw (error);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Check if a signer is listed in the envelope.anchors.
|
|
173
|
+
*/
|
|
174
|
+
function signerListed(envelope, signer) {
|
|
175
|
+
const signerKey = signer.publicKeyString.get();
|
|
176
|
+
return (signerKey in envelope.anchors);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Validate the shape of a {@link AnchorExternalBinding}.
|
|
180
|
+
*
|
|
181
|
+
* @returns `undefined` if valid, or an error message.
|
|
182
|
+
*/
|
|
183
|
+
function validateBindingShape(binding) {
|
|
184
|
+
if (typeof binding.previousBlockHash !== 'string' || binding.previousBlockHash.length === 0) {
|
|
185
|
+
return ('binding.previousBlockHash must be a non-empty string');
|
|
186
|
+
}
|
|
187
|
+
if (!Number.isInteger(binding.operationIndex) || binding.operationIndex < 0) {
|
|
188
|
+
return (`binding.operationIndex must be a non-negative integer, got ${binding.operationIndex}`);
|
|
189
|
+
}
|
|
190
|
+
return (undefined);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Decode the base64-encoded string.
|
|
194
|
+
*/
|
|
195
|
+
function decodeExternal(external) {
|
|
196
|
+
const decoded = decodeBase64Strict(external);
|
|
197
|
+
if (decoded === undefined) {
|
|
198
|
+
throw (new AnchorExternalError('BAD_BASE64', 'External string is not valid base64 or decoded to zero bytes'));
|
|
199
|
+
}
|
|
200
|
+
return (decoded);
|
|
201
|
+
}
|
|
202
|
+
// #endregion
|
|
203
|
+
// #region AnchorExternalBuilder
|
|
204
|
+
/**
|
|
205
|
+
* Fluent builder for constructing an anchor-external envelope and producing
|
|
206
|
+
* its encoded SEND.external string.
|
|
207
|
+
*/
|
|
208
|
+
export class AnchorExternalBuilder {
|
|
209
|
+
#anchors = {};
|
|
210
|
+
#signer;
|
|
211
|
+
#principals;
|
|
212
|
+
#maxLength;
|
|
213
|
+
#binding;
|
|
214
|
+
/**
|
|
215
|
+
* Set the entry for a given anchor, replacing any prior entry
|
|
216
|
+
* for the same anchor.
|
|
217
|
+
*/
|
|
218
|
+
setAnchor(anchor, entry) {
|
|
219
|
+
this.#anchors[anchor.publicKeyString.get()] = entry;
|
|
220
|
+
return (this);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Sign the produced envelope with the given account.
|
|
224
|
+
*/
|
|
225
|
+
withSigner(signer) {
|
|
226
|
+
this.#signer = signer;
|
|
227
|
+
return (this);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Encrypt the produced envelope so each listed principal can decrypt
|
|
231
|
+
* it. Omit to leave the envelope as plaintext.
|
|
232
|
+
*/
|
|
233
|
+
withPrincipals(principals) {
|
|
234
|
+
this.#principals = principals;
|
|
235
|
+
return (this);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Bind the produced signature to a position on the signer's
|
|
239
|
+
* account chain. Required whenever {@link withSigner} is used.
|
|
240
|
+
*
|
|
241
|
+
* @param previousBlockHash Block hash of the signer's current head.
|
|
242
|
+
* @param operationIndex Index of the SEND operation within its block.
|
|
243
|
+
*/
|
|
244
|
+
withBinding(previousBlockHash, operationIndex) {
|
|
245
|
+
const candidate = { previousBlockHash, operationIndex };
|
|
246
|
+
const error = validateBindingShape(candidate);
|
|
247
|
+
if (error !== undefined) {
|
|
248
|
+
throw (new KeetaAnchorError(`withBinding: ${error}`));
|
|
249
|
+
}
|
|
250
|
+
this.#binding = candidate;
|
|
251
|
+
return (this);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Set an upper bound on the encoded output length. {@link build}
|
|
255
|
+
* throws if the produced string would exceed this length.
|
|
256
|
+
*/
|
|
257
|
+
withMaxLength(maxLength) {
|
|
258
|
+
if (!Number.isInteger(maxLength) || maxLength <= 0) {
|
|
259
|
+
throw (new KeetaAnchorError(`withMaxLength requires a positive integer, got ${maxLength}`));
|
|
260
|
+
}
|
|
261
|
+
this.#maxLength = maxLength;
|
|
262
|
+
return (this);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Snapshot of the envelope as it would be encoded right now.
|
|
266
|
+
*/
|
|
267
|
+
toEnvelope() {
|
|
268
|
+
const result = {
|
|
269
|
+
version: ANCHOR_EXTERNAL_VERSION,
|
|
270
|
+
anchors: { ...this.#anchors }
|
|
271
|
+
};
|
|
272
|
+
if (this.#binding !== undefined) {
|
|
273
|
+
result.binding = { ...this.#binding };
|
|
274
|
+
}
|
|
275
|
+
return (result);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Produce the encoded SEND.external string.
|
|
279
|
+
*
|
|
280
|
+
* @throws {@link KeetaAnchorError} on caller misuse.
|
|
281
|
+
*/
|
|
282
|
+
async build() {
|
|
283
|
+
const envelope = this.toEnvelope();
|
|
284
|
+
if (this.#signer !== undefined && !signerListed(envelope, this.#signer)) {
|
|
285
|
+
throw (new KeetaAnchorError('Signer is not listed in envelope.anchors'));
|
|
286
|
+
}
|
|
287
|
+
if (this.#signer !== undefined && this.#binding === undefined) {
|
|
288
|
+
throw (new KeetaAnchorError('Signed envelopes require withBinding(previousBlockHash, operationIndex) for replay protection'));
|
|
289
|
+
}
|
|
290
|
+
if (this.#signer === undefined && this.#binding !== undefined) {
|
|
291
|
+
throw (new KeetaAnchorError('withBinding is only valid for signed envelopes'));
|
|
292
|
+
}
|
|
293
|
+
const encoded = envelopeToEncoded(envelope);
|
|
294
|
+
const canonical = canonicalizeJson(encoded);
|
|
295
|
+
const plaintext = Buffer.from(canonical, 'utf-8');
|
|
296
|
+
const principals = this.#principals ?? null;
|
|
297
|
+
let containerOptions;
|
|
298
|
+
if (this.#signer !== undefined) {
|
|
299
|
+
containerOptions = { signer: this.#signer };
|
|
300
|
+
}
|
|
301
|
+
const container = EncryptedContainer.fromPlaintext(plaintext, principals, containerOptions);
|
|
302
|
+
const containerBuffer = arrayBufferToBuffer(await container.getEncodedBuffer());
|
|
303
|
+
const external = containerBuffer.toString('base64');
|
|
304
|
+
if (this.#maxLength !== undefined && external.length > this.#maxLength) {
|
|
305
|
+
throw (new KeetaAnchorError(`Encoded external length ${external.length} exceeds caller maxLength ${this.#maxLength}`));
|
|
306
|
+
}
|
|
307
|
+
return (external);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// #endregion
|
|
311
|
+
// #region AnchorExternal
|
|
312
|
+
/**
|
|
313
|
+
* Immutable, verified view of a parsed SEND.external envelope.
|
|
314
|
+
*/
|
|
315
|
+
export class AnchorExternal {
|
|
316
|
+
static Builder = AnchorExternalBuilder;
|
|
317
|
+
#envelope;
|
|
318
|
+
#encrypted;
|
|
319
|
+
#signed;
|
|
320
|
+
constructor(envelope, encrypted, signed) {
|
|
321
|
+
this.#envelope = envelope;
|
|
322
|
+
this.#encrypted = encrypted;
|
|
323
|
+
this.#signed = signed;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Decoded envelope.
|
|
327
|
+
*/
|
|
328
|
+
get envelope() {
|
|
329
|
+
return (this.#envelope);
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* `true` if the source blob was encrypted.
|
|
333
|
+
*/
|
|
334
|
+
get encrypted() {
|
|
335
|
+
return (this.#encrypted);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Authenticity record for a verified signed envelope, or `undefined`
|
|
339
|
+
* if the envelope was not signed.
|
|
340
|
+
*/
|
|
341
|
+
get signed() {
|
|
342
|
+
return (this.#signed);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Decode an external string asserted to be plaintext.
|
|
346
|
+
*
|
|
347
|
+
* @throws {@link AnchorExternalError} `EXPECTED_PLAIN` if the source blob is encrypted.
|
|
348
|
+
*/
|
|
349
|
+
static async fromPlainExternal(external) {
|
|
350
|
+
const buffer = decodeExternal(external);
|
|
351
|
+
let container;
|
|
352
|
+
try {
|
|
353
|
+
container = EncryptedContainer.fromEncodedBuffer(buffer, null);
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
if (EncryptedContainerError.isInstance(error)) {
|
|
357
|
+
if (error.code === 'INVALID_PRINCIPALS') {
|
|
358
|
+
throw (new AnchorExternalError('EXPECTED_PLAIN', 'Blob is encrypted but plaintext was expected'));
|
|
359
|
+
}
|
|
360
|
+
throw (new AnchorExternalError('NOT_AN_ENVELOPE', `Container parse failed: ${error.code}`));
|
|
361
|
+
}
|
|
362
|
+
throw (error);
|
|
363
|
+
}
|
|
364
|
+
if (container.encrypted) {
|
|
365
|
+
throw (new AnchorExternalError('EXPECTED_PLAIN', 'Blob is encrypted but plaintext was expected'));
|
|
366
|
+
}
|
|
367
|
+
const result = await AnchorExternal.fromContainer(container, false);
|
|
368
|
+
return (result);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Decode an external string asserted to be encrypted under one of the
|
|
372
|
+
* supplied principals.
|
|
373
|
+
*
|
|
374
|
+
* @throws {@link AnchorExternalError} `EXPECTED_ENCRYPTED` if the source blob is plaintext.
|
|
375
|
+
* @throws {@link KeetaAnchorError} if `principals` is empty.
|
|
376
|
+
*/
|
|
377
|
+
static async fromEncryptedExternal(external, principals) {
|
|
378
|
+
if (principals.length === 0) {
|
|
379
|
+
throw (new KeetaAnchorError('AnchorExternal.fromEncryptedExternal requires at least one principal'));
|
|
380
|
+
}
|
|
381
|
+
const buffer = decodeExternal(external);
|
|
382
|
+
let container;
|
|
383
|
+
try {
|
|
384
|
+
container = EncryptedContainer.fromEncryptedBuffer(buffer, principals);
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
if (EncryptedContainerError.isInstance(error)) {
|
|
388
|
+
if (error.code === 'ENCRYPTION_REQUIRED') {
|
|
389
|
+
throw (new AnchorExternalError('EXPECTED_ENCRYPTED', 'Blob is plaintext but encrypted was expected'));
|
|
390
|
+
}
|
|
391
|
+
throw (new AnchorExternalError('NOT_AN_ENVELOPE', `Container parse failed: ${error.code}`));
|
|
392
|
+
}
|
|
393
|
+
throw (error);
|
|
394
|
+
}
|
|
395
|
+
if (!container.encrypted) {
|
|
396
|
+
throw (new AnchorExternalError('EXPECTED_ENCRYPTED', 'Blob is plaintext but encrypted was expected'));
|
|
397
|
+
}
|
|
398
|
+
const result = await AnchorExternal.fromContainer(container, true);
|
|
399
|
+
return (result);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Inspect encrypted/signed flags of a candidate external string
|
|
403
|
+
* without reading plaintext or requiring a decryption key.
|
|
404
|
+
*/
|
|
405
|
+
static async peek(external) {
|
|
406
|
+
const buffer = decodeExternal(external);
|
|
407
|
+
let container;
|
|
408
|
+
try {
|
|
409
|
+
container = EncryptedContainer.fromEncodedBuffer(buffer, []);
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
if (EncryptedContainerError.isInstance(error)) {
|
|
413
|
+
throw (new AnchorExternalError('NOT_AN_ENVELOPE', `Container parse failed: ${error.code}`));
|
|
414
|
+
}
|
|
415
|
+
throw (error);
|
|
416
|
+
}
|
|
417
|
+
return ({
|
|
418
|
+
encrypted: container.encrypted,
|
|
419
|
+
signed: container.isSigned
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Create an {@link AnchorExternal} from a container.
|
|
424
|
+
*/
|
|
425
|
+
static async fromContainer(container, encrypted) {
|
|
426
|
+
const envelope = await AnchorExternal.readEnvelope(container);
|
|
427
|
+
const signed = await AnchorExternal.readSigned(container, envelope);
|
|
428
|
+
const result = new AnchorExternal(envelope, encrypted, signed);
|
|
429
|
+
return (result);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Read the envelope from a container.
|
|
433
|
+
*/
|
|
434
|
+
static async readEnvelope(container) {
|
|
435
|
+
let plaintextArrayBuffer;
|
|
436
|
+
try {
|
|
437
|
+
plaintextArrayBuffer = await container.getPlaintext();
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
if (EncryptedContainerError.isInstance(error)) {
|
|
441
|
+
throw (new AnchorExternalError('NOT_AN_ENVELOPE', `Container plaintext unavailable: ${error.code}`));
|
|
442
|
+
}
|
|
443
|
+
throw (error);
|
|
444
|
+
}
|
|
445
|
+
if (plaintextArrayBuffer.byteLength > MAX_PLAINTEXT_BYTES) {
|
|
446
|
+
throw (new AnchorExternalError('PLAINTEXT_TOO_LARGE', `Container plaintext exceeds ${MAX_PLAINTEXT_BYTES} bytes`));
|
|
447
|
+
}
|
|
448
|
+
const plaintextBuffer = arrayBufferToBuffer(plaintextArrayBuffer);
|
|
449
|
+
const plaintextString = plaintextBuffer.toString('utf-8');
|
|
450
|
+
let parsed;
|
|
451
|
+
try {
|
|
452
|
+
parsed = JSON.parse(plaintextString);
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
throw (new AnchorExternalError('NOT_AN_ENVELOPE', 'Container plaintext is not valid JSON'));
|
|
456
|
+
}
|
|
457
|
+
const encoded = parseEncodedEnvelope(parsed);
|
|
458
|
+
/*
|
|
459
|
+
* Reject non-canonical encodings: an attacker could otherwise
|
|
460
|
+
* mutate the JSON shape under a still-valid signature on the
|
|
461
|
+
* original bytes.
|
|
462
|
+
*/
|
|
463
|
+
const reCanonical = canonicalizeJson(encoded);
|
|
464
|
+
if (reCanonical !== plaintextString) {
|
|
465
|
+
throw (new AnchorExternalError('NON_CANONICAL', 'Container plaintext is not JCS-canonical'));
|
|
466
|
+
}
|
|
467
|
+
const result = envelopeFromEncoded(encoded);
|
|
468
|
+
return (result);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Read the signed record from a container.
|
|
472
|
+
*/
|
|
473
|
+
static async readSigned(container, envelope) {
|
|
474
|
+
if (!container.isSigned) {
|
|
475
|
+
if (envelope.binding !== undefined) {
|
|
476
|
+
throw (new AnchorExternalError('UNEXPECTED_BINDING', 'Unsigned envelope carries a binding'));
|
|
477
|
+
}
|
|
478
|
+
return (undefined);
|
|
479
|
+
}
|
|
480
|
+
if (envelope.binding === undefined) {
|
|
481
|
+
throw (new AnchorExternalError('MISSING_BINDING', 'Signed envelope is missing required binding'));
|
|
482
|
+
}
|
|
483
|
+
let valid;
|
|
484
|
+
try {
|
|
485
|
+
valid = await container.verifySignature();
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
if (EncryptedContainerError.isInstance(error)) {
|
|
489
|
+
throw (new AnchorExternalError('BAD_SIGNATURE', `Signature verification failed: ${error.code}`));
|
|
490
|
+
}
|
|
491
|
+
throw (error);
|
|
492
|
+
}
|
|
493
|
+
if (!valid) {
|
|
494
|
+
throw (new AnchorExternalError('BAD_SIGNATURE', 'Signature did not verify against the contained plaintext'));
|
|
495
|
+
}
|
|
496
|
+
const signer = container.getSigningAccount();
|
|
497
|
+
if (signer === undefined) {
|
|
498
|
+
throw (new AnchorExternalError('BAD_SIGNATURE', 'Container is signed but the signing account is unavailable'));
|
|
499
|
+
}
|
|
500
|
+
if (!signerListed(envelope, signer)) {
|
|
501
|
+
throw (new AnchorExternalError('SIGNER_NOT_LISTED', 'Signing account is not listed in envelope.anchors'));
|
|
502
|
+
}
|
|
503
|
+
return ({ signer });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
//# sourceMappingURL=anchor-external.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchor-external.js","sourceRoot":"","sources":["../../src/lib/anchor-external.ts"],"names":[],"mappings":"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","sourcesContent":["import type { lib as KeetaNetLib } from '@keetanetwork/keetanet-client';\n\nimport { assertEncodedAnchorExternalEnvelopeV1 } from './anchor-external.generated.js';\nimport { EncryptedContainer, EncryptedContainerError } from './encrypted-container.js';\nimport { KeetaAnchorError, KeetaAnchorUserError, KeetaAnchorUserValidationError } from './error.js';\nimport { canonicalizeJson } from './utils/signing.js';\nimport { Buffer, arrayBufferToBuffer, decodeBase64Strict } from './utils/buffer.js';\n\ntype Account = InstanceType<typeof KeetaNetLib.Account>;\n\n/**\n * Upper bound on inflated container plaintext, before JSON parsing.\n *\n * Prevents an attacker from forcing a large allocation via a\n * high-ratio gzip payload.\n */\nconst MAX_PLAINTEXT_BYTES = 4096;\n\n/**\n * Current envelope format version.\n */\nexport const ANCHOR_EXTERNAL_VERSION = 1;\n\n// #region Public types\n\n/**\n * Per-anchor entry.\n */\nexport type AnchorExternalEntry =\n\t| {\n\t\t/**\n\t\t * Transaction id at the anchor.\n\t\t */\n\t\ttransactionId: string;\n\t}\n\t| {\n\t\t/**\n\t\t * Persistent forwarding id at the anchor.\n\t\t */\n\t\tpersistentForwardingId: string;\n\t}\n\t| {\n\t\t/**\n\t\t * Opaque destination data interpreted by the anchor (e.g. an EVM\n\t\t * address for an EVM anchor).\n\t\t */\n\t\tdestination: string;\n\t};\n\n/**\n * Replay-protection binding for a signed envelope.\n *\n * Pins the signature to a specific position on the signer's account\n * chain so the same signed envelope cannot be reused on a different\n * operation or block.\n */\nexport type AnchorExternalBinding = {\n\t/**\n\t * Block hash that the signer's account had as its head when the\n\t * SEND carrying this envelope was constructed.\n\t */\n\tpreviousBlockHash: string;\n\t/**\n\t * Index of the SEND operation within its block.\n\t */\n\toperationIndex: number;\n};\n\n/**\n * Decoded anchor-external envelope as exposed to callers.\n */\nexport type AnchorExternalEnvelope = {\n\t/**\n\t * Envelope format version.\n\t */\n\tversion: typeof ANCHOR_EXTERNAL_VERSION;\n\t/**\n\t * Per-anchor entries keyed by `Account.publicKeyString.get()`.\n\t */\n\tanchors: { [anchorPublicKey: string]: AnchorExternalEntry };\n\t/**\n\t * Signature binding. Present if the envelope is signed.\n\t */\n\tbinding?: AnchorExternalBinding;\n};\n\n/**\n * Authenticity record attached to a verified signed envelope. Present only\n * when the signature was verified and the signer appears in\n * {@link AnchorExternalEnvelope.anchors}.\n */\nexport type AnchorExternalSigned = {\n\tsigner: Account;\n};\n\n/**\n * Shape inspection result from {@link AnchorExternal.peek}.\n */\nexport type AnchorExternalPeekResult = {\n\tencrypted: boolean;\n\tsigned: boolean;\n};\n\n// #endregion\n\n// #region Encoded form types (module-internal, exported for tests)\n\n/**\n * Per-anchor entry as it appears in the encoded envelope.\n *\n * `t` = transaction id, `p` = persistent forwarding id, `d` = destination.\n *\n * Note: Single-letter keys keep the envelope smaller.\n */\nexport type EncodedAnchorExternalEntryV1 =\n\t| { t: string }\n\t| { p: string }\n\t| { d: string };\n\n/**\n * Replay-protection binding as it appears in the encoded envelope.\n *\n * `p` = previous block hash, `o` = operation index.\n */\nexport type EncodedAnchorExternalBindingV1 = {\n\tp: string;\n\to: number;\n};\n\n/**\n * Anchor-external envelope as it appears inside the encoded blob.\n *\n * `v` = envelope version, `a` = anchors, `b` = binding.\n */\nexport type EncodedAnchorExternalEnvelopeV1 = {\n\tv: typeof ANCHOR_EXTERNAL_VERSION;\n\ta: { [anchorPublicKey: string]: EncodedAnchorExternalEntryV1 };\n\tb?: EncodedAnchorExternalBindingV1;\n};\n\n// #endregion\n\n// #region Error Handling\n\n/**\n * Error codes for anchor-external envelope operations.\n */\nexport const AnchorExternalErrorCodes = [\n\t/*\n\t * Parsing\n\t */\n\t'BAD_BASE64',\n\t'NOT_AN_ENVELOPE',\n\t'UNSUPPORTED_VERSION',\n\t'NON_CANONICAL',\n\t'PLAINTEXT_TOO_LARGE',\n\n\t/*\n\t * Non-repudiation\n\t */\n\t'BAD_SIGNATURE',\n\t'SIGNER_NOT_LISTED',\n\t'MISSING_BINDING',\n\t'UNEXPECTED_BINDING',\n\n\t/*\n\t * Confidentiality\n\t */\n\t'EXPECTED_PLAIN',\n\t'EXPECTED_ENCRYPTED',\n\n\t/*\n\t * Caller-declared budget\n\t */\n\t'OUTPUT_TOO_LARGE'\n] as const;\n\nexport type AnchorExternalErrorCode = typeof AnchorExternalErrorCodes[number];\n\nconst anchorExternalErrorCodeSet: ReadonlySet<string> = new Set(AnchorExternalErrorCodes);\n\n/**\n * Error raised by anchor-external decode failures.\n */\nexport class AnchorExternalError extends KeetaAnchorUserError {\n\tstatic override readonly name: string = 'AnchorExternalError';\n\n\tprivate readonly anchorExternalErrorObjectTypeID!: string;\n\tprivate static readonly anchorExternalErrorObjectTypeID = '2db22831-216b-4b3e-952a-5f9860f0790b';\n\n\treadonly code: AnchorExternalErrorCode;\n\n\t/**\n\t * Type-narrow an arbitrary string to {@link AnchorExternalErrorCode}.\n\t */\n\tstatic isValidCode(code: string): code is AnchorExternalErrorCode {\n\t\treturn(anchorExternalErrorCodeSet.has(code));\n\t}\n\n\tconstructor(code: AnchorExternalErrorCode, message: string) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\n\t\tObject.defineProperty(this, 'anchorExternalErrorObjectTypeID', {\n\t\t\tvalue: AnchorExternalError.anchorExternalErrorObjectTypeID,\n\t\t\tenumerable: false\n\t\t});\n\t}\n\n\tstatic override isInstance(input: unknown): input is AnchorExternalError {\n\t\treturn(this.hasPropWithValue(input, 'anchorExternalErrorObjectTypeID', AnchorExternalError.anchorExternalErrorObjectTypeID));\n\t}\n\n\toverride toJSON(): ReturnType<KeetaAnchorUserError['toJSON']> & { code: AnchorExternalErrorCode } {\n\t\treturn({\n\t\t\t...super.toJSON(),\n\t\t\tcode: this.code\n\t\t});\n\t}\n\n\tstatic override async fromJSON(input: unknown): Promise<AnchorExternalError> {\n\t\tconst { message, other } = this.extractErrorProperties(input, this);\n\n\t\tif (!('code' in other) || typeof other.code !== 'string') {\n\t\t\tthrow(new Error('Invalid AnchorExternalError JSON: missing code property'));\n\t\t}\n\n\t\tconst code = other.code;\n\t\tif (!this.isValidCode(code)) {\n\t\t\tthrow(new Error(`Invalid AnchorExternalError JSON: unknown code ${code}`));\n\t\t}\n\n\t\tconst error = new this(code, message);\n\t\terror.restoreFromJSON(other);\n\t\treturn(error);\n\t}\n}\n\n// #endregion\n\n// #region Encoded <-> Public mappers\n\n/**\n * Convert a public entry to an encoded entry.\n */\nfunction entryToEncoded(entry: AnchorExternalEntry): EncodedAnchorExternalEntryV1 {\n\tif ('transactionId' in entry) {\n\t\treturn({ t: entry.transactionId });\n\t}\n\tif ('persistentForwardingId' in entry) {\n\t\treturn({ p: entry.persistentForwardingId });\n\t}\n\n\treturn({ d: entry.destination });\n}\n\n/**\n * Convert an encoded entry to a public entry.\n */\nfunction entryFromEncoded(entry: EncodedAnchorExternalEntryV1): AnchorExternalEntry {\n\tif ('t' in entry) {\n\t\treturn({ transactionId: entry.t });\n\t}\n\tif ('p' in entry) {\n\t\treturn({ persistentForwardingId: entry.p });\n\t}\n\n\treturn({ destination: entry.d });\n}\n\n/**\n * Convert a public envelope to an encoded envelope.\n */\nfunction envelopeToEncoded(envelope: AnchorExternalEnvelope): EncodedAnchorExternalEnvelopeV1 {\n\tconst encodedAnchors: { [anchorPublicKey: string]: EncodedAnchorExternalEntryV1 } = {};\n\tfor (const [pk, entry] of Object.entries(envelope.anchors)) {\n\t\tencodedAnchors[pk] = entryToEncoded(entry);\n\t}\n\n\tconst result: EncodedAnchorExternalEnvelopeV1 = {\n\t\tv: ANCHOR_EXTERNAL_VERSION,\n\t\ta: encodedAnchors\n\t};\n\tif (envelope.binding !== undefined) {\n\t\tresult.b = { p: envelope.binding.previousBlockHash, o: envelope.binding.operationIndex };\n\t}\n\n\treturn(result);\n}\n\n/**\n * Convert an encoded envelope to a public envelope.\n */\nfunction envelopeFromEncoded(encoded: EncodedAnchorExternalEnvelopeV1): AnchorExternalEnvelope {\n\tconst anchors: { [anchorPublicKey: string]: AnchorExternalEntry } = {};\n\tfor (const [pk, entry] of Object.entries(encoded.a)) {\n\t\tanchors[pk] = entryFromEncoded(entry);\n\t}\n\n\tconst result: AnchorExternalEnvelope = {\n\t\tversion: ANCHOR_EXTERNAL_VERSION,\n\t\tanchors\n\t};\n\tif (encoded.b !== undefined) {\n\t\tresult.binding = { previousBlockHash: encoded.b.p, operationIndex: encoded.b.o };\n\t}\n\n\treturn(result);\n}\n\n/**\n * Parse an encoded envelope.\n */\nfunction parseEncodedEnvelope(value: unknown): EncodedAnchorExternalEnvelopeV1 {\n\ttry {\n\t\treturn(assertEncodedAnchorExternalEnvelopeV1(value));\n\t} catch (error) {\n\t\tif (KeetaAnchorUserValidationError.isTypeGuardErrorLike(error)) {\n\t\t\tif (error.path === '$input.v') {\n\t\t\t\tthrow(new AnchorExternalError('UNSUPPORTED_VERSION', `Unsupported envelope version at ${error.path}: ${String(error.value)}`));\n\t\t\t}\n\n\t\t\tthrow(new AnchorExternalError('NOT_AN_ENVELOPE', `Envelope failed shape check at ${error.path ?? '$input'}: expected ${error.expected}`));\n\t\t}\n\n\t\tthrow(error);\n\t}\n}\n\n/**\n * Check if a signer is listed in the envelope.anchors.\n */\nfunction signerListed(envelope: AnchorExternalEnvelope, signer: Account): boolean {\n\tconst signerKey = signer.publicKeyString.get();\n\treturn(signerKey in envelope.anchors);\n}\n\n/**\n * Validate the shape of a {@link AnchorExternalBinding}.\n *\n * @returns `undefined` if valid, or an error message.\n */\nfunction validateBindingShape(binding: AnchorExternalBinding): string | undefined {\n\tif (typeof binding.previousBlockHash !== 'string' || binding.previousBlockHash.length === 0) {\n\t\treturn('binding.previousBlockHash must be a non-empty string');\n\t}\n\tif (!Number.isInteger(binding.operationIndex) || binding.operationIndex < 0) {\n\t\treturn(`binding.operationIndex must be a non-negative integer, got ${binding.operationIndex}`);\n\t}\n\n\treturn(undefined);\n}\n\n/**\n * Decode the base64-encoded string.\n */\nfunction decodeExternal(external: string): Buffer {\n\tconst decoded = decodeBase64Strict(external);\n\tif (decoded === undefined) {\n\t\tthrow(new AnchorExternalError('BAD_BASE64', 'External string is not valid base64 or decoded to zero bytes'));\n\t}\n\n\treturn(decoded);\n}\n\n// #endregion\n\n// #region AnchorExternalBuilder\n\n/**\n * Fluent builder for constructing an anchor-external envelope and producing\n * its encoded SEND.external string.\n */\nexport class AnchorExternalBuilder {\n\treadonly #anchors: { [anchorPublicKey: string]: AnchorExternalEntry } = {};\n\t#signer: Account | undefined;\n\t#principals: Account[] | undefined;\n\t#maxLength: number | undefined;\n\t#binding: AnchorExternalBinding | undefined;\n\n\t/**\n\t * Set the entry for a given anchor, replacing any prior entry\n\t * for the same anchor.\n\t */\n\tsetAnchor(anchor: Account, entry: AnchorExternalEntry): this {\n\t\tthis.#anchors[anchor.publicKeyString.get()] = entry;\n\t\treturn(this);\n\t}\n\n\t/**\n\t * Sign the produced envelope with the given account.\n\t */\n\twithSigner(signer: Account): this {\n\t\tthis.#signer = signer;\n\t\treturn(this);\n\t}\n\n\t/**\n\t * Encrypt the produced envelope so each listed principal can decrypt\n\t * it. Omit to leave the envelope as plaintext.\n\t */\n\twithPrincipals(principals: Account[]): this {\n\t\tthis.#principals = principals;\n\t\treturn(this);\n\t}\n\n\t/**\n\t * Bind the produced signature to a position on the signer's\n\t * account chain. Required whenever {@link withSigner} is used.\n\t *\n\t * @param previousBlockHash Block hash of the signer's current head.\n\t * @param operationIndex Index of the SEND operation within its block.\n\t */\n\twithBinding(previousBlockHash: string, operationIndex: number): this {\n\t\tconst candidate: AnchorExternalBinding = { previousBlockHash, operationIndex };\n\t\tconst error = validateBindingShape(candidate);\n\t\tif (error !== undefined) {\n\t\t\tthrow(new KeetaAnchorError(`withBinding: ${error}`));\n\t\t}\n\n\t\tthis.#binding = candidate;\n\t\treturn(this);\n\t}\n\n\t/**\n\t * Set an upper bound on the encoded output length. {@link build}\n\t * throws if the produced string would exceed this length.\n\t */\n\twithMaxLength(maxLength: number): this {\n\t\tif (!Number.isInteger(maxLength) || maxLength <= 0) {\n\t\t\tthrow(new KeetaAnchorError(`withMaxLength requires a positive integer, got ${maxLength}`));\n\t\t}\n\n\t\tthis.#maxLength = maxLength;\n\t\treturn(this);\n\t}\n\n\t/**\n\t * Snapshot of the envelope as it would be encoded right now.\n\t */\n\ttoEnvelope(): AnchorExternalEnvelope {\n\t\tconst result: AnchorExternalEnvelope = {\n\t\t\tversion: ANCHOR_EXTERNAL_VERSION,\n\t\t\tanchors: { ...this.#anchors }\n\t\t};\n\n\t\tif (this.#binding !== undefined) {\n\t\t\tresult.binding = { ...this.#binding };\n\t\t}\n\n\t\treturn(result);\n\t}\n\n\t/**\n\t * Produce the encoded SEND.external string.\n\t *\n\t * @throws {@link KeetaAnchorError} on caller misuse.\n\t */\n\tasync build(): Promise<string> {\n\t\tconst envelope = this.toEnvelope();\n\n\t\tif (this.#signer !== undefined && !signerListed(envelope, this.#signer)) {\n\t\t\tthrow(new KeetaAnchorError('Signer is not listed in envelope.anchors'));\n\t\t}\n\t\tif (this.#signer !== undefined && this.#binding === undefined) {\n\t\t\tthrow(new KeetaAnchorError('Signed envelopes require withBinding(previousBlockHash, operationIndex) for replay protection'));\n\t\t}\n\t\tif (this.#signer === undefined && this.#binding !== undefined) {\n\t\t\tthrow(new KeetaAnchorError('withBinding is only valid for signed envelopes'));\n\t\t}\n\n\t\tconst encoded = envelopeToEncoded(envelope);\n\t\tconst canonical = canonicalizeJson(encoded);\n\t\tconst plaintext = Buffer.from(canonical, 'utf-8');\n\n\t\tconst principals = this.#principals ?? null;\n\t\tlet containerOptions: { signer: Account } | undefined;\n\t\tif (this.#signer !== undefined) {\n\t\t\tcontainerOptions = { signer: this.#signer };\n\t\t}\n\n\t\tconst container = EncryptedContainer.fromPlaintext(plaintext, principals, containerOptions);\n\t\tconst containerBuffer = arrayBufferToBuffer(await container.getEncodedBuffer());\n\t\tconst external = containerBuffer.toString('base64');\n\n\t\tif (this.#maxLength !== undefined && external.length > this.#maxLength) {\n\t\t\tthrow(new KeetaAnchorError(`Encoded external length ${external.length} exceeds caller maxLength ${this.#maxLength}`));\n\t\t}\n\n\t\treturn(external);\n\t}\n}\n\n// #endregion\n\n// #region AnchorExternal\n\n/**\n * Immutable, verified view of a parsed SEND.external envelope.\n */\nexport class AnchorExternal {\n\tstatic readonly Builder: typeof AnchorExternalBuilder = AnchorExternalBuilder;\n\n\treadonly #envelope: AnchorExternalEnvelope;\n\treadonly #encrypted: boolean;\n\treadonly #signed: AnchorExternalSigned | undefined;\n\n\tprivate constructor(envelope: AnchorExternalEnvelope, encrypted: boolean, signed: AnchorExternalSigned | undefined) {\n\t\tthis.#envelope = envelope;\n\t\tthis.#encrypted = encrypted;\n\t\tthis.#signed = signed;\n\t}\n\n\t/**\n\t * Decoded envelope.\n\t */\n\tget envelope(): AnchorExternalEnvelope {\n\t\treturn(this.#envelope);\n\t}\n\n\t/**\n\t * `true` if the source blob was encrypted.\n\t */\n\tget encrypted(): boolean {\n\t\treturn(this.#encrypted);\n\t}\n\n\t/**\n\t * Authenticity record for a verified signed envelope, or `undefined`\n\t * if the envelope was not signed.\n\t */\n\tget signed(): AnchorExternalSigned | undefined {\n\t\treturn(this.#signed);\n\t}\n\n\t/**\n\t * Decode an external string asserted to be plaintext.\n\t *\n\t * @throws {@link AnchorExternalError} `EXPECTED_PLAIN` if the source blob is encrypted.\n\t */\n\tstatic async fromPlainExternal(external: string): Promise<AnchorExternal> {\n\t\tconst buffer = decodeExternal(external);\n\n\t\tlet container: EncryptedContainer;\n\t\ttry {\n\t\t\tcontainer = EncryptedContainer.fromEncodedBuffer(buffer, null);\n\t\t} catch (error) {\n\t\t\tif (EncryptedContainerError.isInstance(error)) {\n\t\t\t\tif (error.code === 'INVALID_PRINCIPALS') {\n\t\t\t\t\tthrow(new AnchorExternalError('EXPECTED_PLAIN', 'Blob is encrypted but plaintext was expected'));\n\t\t\t\t}\n\t\t\t\tthrow(new AnchorExternalError('NOT_AN_ENVELOPE', `Container parse failed: ${error.code}`));\n\t\t\t}\n\t\t\tthrow(error);\n\t\t}\n\n\t\tif (container.encrypted) {\n\t\t\tthrow(new AnchorExternalError('EXPECTED_PLAIN', 'Blob is encrypted but plaintext was expected'));\n\t\t}\n\n\t\tconst result = await AnchorExternal.fromContainer(container, false);\n\t\treturn(result);\n\t}\n\n\t/**\n\t * Decode an external string asserted to be encrypted under one of the\n\t * supplied principals.\n\t *\n\t * @throws {@link AnchorExternalError} `EXPECTED_ENCRYPTED` if the source blob is plaintext.\n\t * @throws {@link KeetaAnchorError} if `principals` is empty.\n\t */\n\tstatic async fromEncryptedExternal(external: string, principals: Account[]): Promise<AnchorExternal> {\n\t\tif (principals.length === 0) {\n\t\t\tthrow(new KeetaAnchorError('AnchorExternal.fromEncryptedExternal requires at least one principal'));\n\t\t}\n\n\t\tconst buffer = decodeExternal(external);\n\t\tlet container: EncryptedContainer;\n\t\ttry {\n\t\t\tcontainer = EncryptedContainer.fromEncryptedBuffer(buffer, principals);\n\t\t} catch (error) {\n\t\t\tif (EncryptedContainerError.isInstance(error)) {\n\t\t\t\tif (error.code === 'ENCRYPTION_REQUIRED') {\n\t\t\t\t\tthrow(new AnchorExternalError('EXPECTED_ENCRYPTED', 'Blob is plaintext but encrypted was expected'));\n\t\t\t\t}\n\n\t\t\t\tthrow(new AnchorExternalError('NOT_AN_ENVELOPE', `Container parse failed: ${error.code}`));\n\t\t\t}\n\n\t\t\tthrow(error);\n\t\t}\n\n\t\tif (!container.encrypted) {\n\t\t\tthrow(new AnchorExternalError('EXPECTED_ENCRYPTED', 'Blob is plaintext but encrypted was expected'));\n\t\t}\n\n\t\tconst result = await AnchorExternal.fromContainer(container, true);\n\t\treturn(result);\n\t}\n\n\t/**\n\t * Inspect encrypted/signed flags of a candidate external string\n\t * without reading plaintext or requiring a decryption key.\n\t */\n\tstatic async peek(external: string): Promise<AnchorExternalPeekResult> {\n\t\tconst buffer = decodeExternal(external);\n\t\tlet container: EncryptedContainer;\n\t\ttry {\n\t\t\tcontainer = EncryptedContainer.fromEncodedBuffer(buffer, []);\n\t\t} catch (error) {\n\t\t\tif (EncryptedContainerError.isInstance(error)) {\n\t\t\t\tthrow(new AnchorExternalError('NOT_AN_ENVELOPE', `Container parse failed: ${error.code}`));\n\t\t\t}\n\n\t\t\tthrow(error);\n\t\t}\n\n\t\treturn({\n\t\t\tencrypted: container.encrypted,\n\t\t\tsigned: container.isSigned\n\t\t});\n\t}\n\n\t/**\n\t * Create an {@link AnchorExternal} from a container.\n\t */\n\tprivate static async fromContainer(container: EncryptedContainer, encrypted: boolean): Promise<AnchorExternal> {\n\t\tconst envelope = await AnchorExternal.readEnvelope(container);\n\t\tconst signed = await AnchorExternal.readSigned(container, envelope);\n\t\tconst result = new AnchorExternal(envelope, encrypted, signed);\n\t\treturn(result);\n\t}\n\n\t/**\n\t * Read the envelope from a container.\n\t */\n\tprivate static async readEnvelope(container: EncryptedContainer): Promise<AnchorExternalEnvelope> {\n\t\tlet plaintextArrayBuffer: ArrayBuffer;\n\t\ttry {\n\t\t\tplaintextArrayBuffer = await container.getPlaintext();\n\t\t} catch (error) {\n\t\t\tif (EncryptedContainerError.isInstance(error)) {\n\t\t\t\tthrow(new AnchorExternalError('NOT_AN_ENVELOPE', `Container plaintext unavailable: ${error.code}`));\n\t\t\t}\n\t\t\tthrow(error);\n\t\t}\n\n\t\tif (plaintextArrayBuffer.byteLength > MAX_PLAINTEXT_BYTES) {\n\t\t\tthrow(new AnchorExternalError('PLAINTEXT_TOO_LARGE', `Container plaintext exceeds ${MAX_PLAINTEXT_BYTES} bytes`));\n\t\t}\n\n\t\tconst plaintextBuffer = arrayBufferToBuffer(plaintextArrayBuffer);\n\t\tconst plaintextString = plaintextBuffer.toString('utf-8');\n\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(plaintextString);\n\t\t} catch {\n\t\t\tthrow(new AnchorExternalError('NOT_AN_ENVELOPE', 'Container plaintext is not valid JSON'));\n\t\t}\n\n\t\tconst encoded = parseEncodedEnvelope(parsed);\n\n\t\t/*\n\t\t * Reject non-canonical encodings: an attacker could otherwise\n\t\t * mutate the JSON shape under a still-valid signature on the\n\t\t * original bytes.\n\t\t */\n\t\tconst reCanonical = canonicalizeJson(encoded);\n\t\tif (reCanonical !== plaintextString) {\n\t\t\tthrow(new AnchorExternalError('NON_CANONICAL', 'Container plaintext is not JCS-canonical'));\n\t\t}\n\n\t\tconst result = envelopeFromEncoded(encoded);\n\t\treturn(result);\n\t}\n\n\t/**\n\t * Read the signed record from a container.\n\t */\n\tprivate static async readSigned(container: EncryptedContainer, envelope: AnchorExternalEnvelope): Promise<AnchorExternalSigned | undefined> {\n\t\tif (!container.isSigned) {\n\t\t\tif (envelope.binding !== undefined) {\n\t\t\t\tthrow(new AnchorExternalError('UNEXPECTED_BINDING', 'Unsigned envelope carries a binding'));\n\t\t\t}\n\n\t\t\treturn(undefined);\n\t\t}\n\n\t\tif (envelope.binding === undefined) {\n\t\t\tthrow(new AnchorExternalError('MISSING_BINDING', 'Signed envelope is missing required binding'));\n\t\t}\n\n\t\tlet valid: boolean;\n\t\ttry {\n\t\t\tvalid = await container.verifySignature();\n\t\t} catch (error) {\n\t\t\tif (EncryptedContainerError.isInstance(error)) {\n\t\t\t\tthrow(new AnchorExternalError('BAD_SIGNATURE', `Signature verification failed: ${error.code}`));\n\t\t\t}\n\n\t\t\tthrow(error);\n\t\t}\n\n\t\tif (!valid) {\n\t\t\tthrow(new AnchorExternalError('BAD_SIGNATURE', 'Signature did not verify against the contained plaintext'));\n\t\t}\n\n\t\tconst signer = container.getSigningAccount();\n\t\tif (signer === undefined) {\n\t\t\tthrow(new AnchorExternalError('BAD_SIGNATURE', 'Container is signed but the signing account is unavailable'));\n\t\t}\n\n\t\tif (!signerListed(envelope, signer)) {\n\t\t\tthrow(new AnchorExternalError('SIGNER_NOT_LISTED', 'Signing account is not listed in envelope.anchors'));\n\t\t}\n\n\t\treturn({ signer });\n\t}\n}\n\n// #endregion\n"]}
|
package/lib/index.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ import * as Certificates from './certificates.js';
|
|
|
2
2
|
import { EncryptedContainer } from './encrypted-container.js';
|
|
3
3
|
import * as URI from './uri.js';
|
|
4
4
|
import Resolver from './resolver.js';
|
|
5
|
+
export { AnchorExternal } from './anchor-external.js';
|
|
5
6
|
export { Certificates, EncryptedContainer, Resolver, URI };
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAEhC,OAAO,QAAQ,MAAM,eAAe,CAAC;AACrC,OAAO,EACN,YAAY,EACZ,kBAAkB,EAClB,QAAQ,EACR,GAAG,EACH,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAEhC,OAAO,QAAQ,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EACN,YAAY,EACZ,kBAAkB,EAClB,QAAQ,EACR,GAAG,EACH,CAAA"}
|
package/lib/index.js
CHANGED
|
@@ -2,5 +2,6 @@ import * as Certificates from './certificates.js';
|
|
|
2
2
|
import { EncryptedContainer } from './encrypted-container.js';
|
|
3
3
|
import * as URI from './uri.js';
|
|
4
4
|
import Resolver from './resolver.js';
|
|
5
|
+
export { AnchorExternal } from './anchor-external.js';
|
|
5
6
|
export { Certificates, EncryptedContainer, Resolver, URI };
|
|
6
7
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAEhC,OAAO,QAAQ,MAAM,eAAe,CAAC;AACrC,OAAO,EACN,YAAY,EACZ,kBAAkB,EAClB,QAAQ,EACR,GAAG,EACH,CAAA","sourcesContent":["import * as Certificates from './certificates.js';\nimport { EncryptedContainer } from './encrypted-container.js';\nimport * as URI from './uri.js';\n\nimport Resolver from './resolver.js';\nexport {\n\tCertificates,\n\tEncryptedContainer,\n\tResolver,\n\tURI\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAEhC,OAAO,QAAQ,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EACN,YAAY,EACZ,kBAAkB,EAClB,QAAQ,EACR,GAAG,EACH,CAAA","sourcesContent":["import * as Certificates from './certificates.js';\nimport { EncryptedContainer } from './encrypted-container.js';\nimport * as URI from './uri.js';\n\nimport Resolver from './resolver.js';\nexport { AnchorExternal } from './anchor-external.js';\nexport {\n\tCertificates,\n\tEncryptedContainer,\n\tResolver,\n\tURI\n}\n"]}
|
package/lib/utils/buffer.d.ts
CHANGED
|
@@ -4,4 +4,8 @@ export declare const Buffer: typeof KeetaNetLib.Utils.Buffer.Buffer;
|
|
|
4
4
|
export declare const bufferToArrayBuffer: typeof KeetaNetLib.Utils.Helper.bufferToArrayBuffer;
|
|
5
5
|
export declare function arrayBufferToBuffer(arrayBuffer: ArrayBuffer): Buffer;
|
|
6
6
|
export declare function arrayBufferLikeToBuffer(buffer: ArrayBufferLike | ArrayBufferView): Buffer;
|
|
7
|
+
/**
|
|
8
|
+
* Decode a base64 string into a {@link Buffer}.
|
|
9
|
+
*/
|
|
10
|
+
export declare function decodeBase64Strict(input: string): Buffer | undefined;
|
|
7
11
|
//# sourceMappingURL=buffer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buffer.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/buffer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,IAAI,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEnE,MAAM,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAC1E,eAAO,MAAM,MAAM,EAAE,OAAO,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,MAAwC,CAAC;AAM9F,eAAO,MAAM,mBAAmB,EAAE,OAAO,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,mBAAiG,CAAC;AAkBpK,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,CAEpE;AAKD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,GAAG,MAAM,CAMzF"}
|
|
1
|
+
{"version":3,"file":"buffer.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/buffer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,IAAI,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEnE,MAAM,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAC1E,eAAO,MAAM,MAAM,EAAE,OAAO,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,MAAwC,CAAC;AAM9F,eAAO,MAAM,mBAAmB,EAAE,OAAO,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,mBAAiG,CAAC;AAkBpK,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,CAEpE;AAKD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,GAAG,MAAM,CAMzF;AAOD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAWpE"}
|
package/lib/utils/buffer.js
CHANGED
|
@@ -32,4 +32,21 @@ export function arrayBufferLikeToBuffer(buffer) {
|
|
|
32
32
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
33
33
|
return (toBuffer(buffer));
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Regular expression to match base64 input.
|
|
37
|
+
*/
|
|
38
|
+
const BASE64_INPUT_REGEX = /^[-_A-Za-z0-9+/=\s]*$/;
|
|
39
|
+
/**
|
|
40
|
+
* Decode a base64 string into a {@link Buffer}.
|
|
41
|
+
*/
|
|
42
|
+
export function decodeBase64Strict(input) {
|
|
43
|
+
if (!BASE64_INPUT_REGEX.test(input)) {
|
|
44
|
+
return (undefined);
|
|
45
|
+
}
|
|
46
|
+
const decoded = Buffer.from(input, 'base64');
|
|
47
|
+
if (decoded.length === 0) {
|
|
48
|
+
return (undefined);
|
|
49
|
+
}
|
|
50
|
+
return (decoded);
|
|
51
|
+
}
|
|
35
52
|
//# sourceMappingURL=buffer.js.map
|