@defdone/annon 0.0.0-stage → 0.1.0-alpha.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/CHANGELOG.md +56 -0
- package/CLI.md +378 -0
- package/LICENSE +201 -0
- package/NOTICE +7 -0
- package/README.md +581 -2
- package/dist/api/annon-types.d.ts +108 -0
- package/dist/api/annon-types.js +2 -0
- package/dist/api/annon.d.ts +117 -0
- package/dist/api/annon.js +1152 -0
- package/dist/api/hosted-record.d.ts +13 -0
- package/dist/api/hosted-record.js +149 -0
- package/dist/api/sharing-types.d.ts +109 -0
- package/dist/api/sharing-types.js +1 -0
- package/dist/api/sharing.d.ts +36 -0
- package/dist/api/sharing.js +879 -0
- package/dist/browser/download.d.ts +19 -0
- package/dist/browser/download.js +52 -0
- package/dist/browser/index.d.ts +23 -0
- package/dist/browser/index.js +22 -0
- package/dist/browser/login-bundle.js +116 -0
- package/dist/browser/login.d.ts +1 -0
- package/dist/browser/login.js +177 -0
- package/dist/browser/purchase-bundle.js +84 -0
- package/dist/browser/purchases.d.ts +98 -0
- package/dist/browser/purchases.js +735 -0
- package/dist/browser/record.d.ts +55 -0
- package/dist/browser/record.js +16 -0
- package/dist/browser/selling.d.ts +146 -0
- package/dist/browser/selling.js +418 -0
- package/dist/browser/wallets.d.ts +53 -0
- package/dist/browser/wallets.js +286 -0
- package/dist/bytes.d.ts +4 -0
- package/dist/bytes.js +16 -0
- package/dist/download/download.d.ts +59 -0
- package/dist/download/download.js +384 -0
- package/dist/download/host.d.ts +56 -0
- package/dist/download/host.js +161 -0
- package/dist/download/http.d.ts +23 -0
- package/dist/download/http.js +87 -0
- package/dist/download/proof.d.ts +20 -0
- package/dist/download/proof.js +79 -0
- package/dist/download/upload.d.ts +130 -0
- package/dist/download/upload.js +408 -0
- package/dist/envelope/evm.d.ts +4 -0
- package/dist/envelope/evm.js +32 -0
- package/dist/envelope/pay2.d.ts +176 -0
- package/dist/envelope/pay2.js +541 -0
- package/dist/envelope/replay.d.ts +45 -0
- package/dist/envelope/replay.js +186 -0
- package/dist/envelope/stream.d.ts +78 -0
- package/dist/envelope/stream.js +372 -0
- package/dist/errors.d.ts +21 -0
- package/dist/errors.js +300 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +23 -0
- package/dist/keys/near-ckd.d.ts +47 -0
- package/dist/keys/near-ckd.js +96 -0
- package/dist/limits.d.ts +4 -0
- package/dist/limits.js +4 -0
- package/dist/near/nep413.d.ts +22 -0
- package/dist/near/nep413.js +39 -0
- package/dist/near/rpc.d.ts +170 -0
- package/dist/near/rpc.js +423 -0
- package/dist/near/texts.d.ts +39 -0
- package/dist/near/texts.js +60 -0
- package/dist/net/ip.d.ts +7 -0
- package/dist/net/ip.js +62 -0
- package/dist/net/retry.d.ts +3 -0
- package/dist/net/retry.js +31 -0
- package/dist/node/adapter-checks.d.ts +6 -0
- package/dist/node/adapter-checks.js +13 -0
- package/dist/node/adapter.d.ts +8 -0
- package/dist/node/adapter.js +99 -0
- package/dist/node/agent.d.ts +49 -0
- package/dist/node/agent.js +266 -0
- package/dist/node/amounts.d.ts +2 -0
- package/dist/node/amounts.js +20 -0
- package/dist/node/browser-login.d.ts +18 -0
- package/dist/node/browser-login.js +212 -0
- package/dist/node/budget.d.ts +24 -0
- package/dist/node/budget.js +59 -0
- package/dist/node/buy-commands.d.ts +24 -0
- package/dist/node/buy-commands.js +301 -0
- package/dist/node/cli.d.ts +60 -0
- package/dist/node/cli.js +201 -0
- package/dist/node/command-context.d.ts +82 -0
- package/dist/node/command-context.js +77 -0
- package/dist/node/config.d.ts +37 -0
- package/dist/node/config.js +189 -0
- package/dist/node/core-adapter.d.ts +43 -0
- package/dist/node/core-adapter.js +122 -0
- package/dist/node/errors.d.ts +8 -0
- package/dist/node/errors.js +12 -0
- package/dist/node/index.d.ts +7 -0
- package/dist/node/index.js +5 -0
- package/dist/node/io.d.ts +13 -0
- package/dist/node/io.js +112 -0
- package/dist/node/keychain.d.ts +10 -0
- package/dist/node/keychain.js +67 -0
- package/dist/node/main.d.ts +2 -0
- package/dist/node/main.js +21 -0
- package/dist/node/offers.d.ts +16 -0
- package/dist/node/offers.js +91 -0
- package/dist/node/origin.d.ts +11 -0
- package/dist/node/origin.js +80 -0
- package/dist/node/paths.d.ts +3 -0
- package/dist/node/paths.js +71 -0
- package/dist/node/payment-terms.d.ts +26 -0
- package/dist/node/payment-terms.js +127 -0
- package/dist/node/purchase-commands.d.ts +16 -0
- package/dist/node/purchase-commands.js +237 -0
- package/dist/node/purchase-guard.d.ts +19 -0
- package/dist/node/purchase-guard.js +53 -0
- package/dist/node/relay-transport.d.ts +6 -0
- package/dist/node/relay-transport.js +173 -0
- package/dist/node/resources.d.ts +2 -0
- package/dist/node/resources.js +26 -0
- package/dist/node/retry-gate.d.ts +15 -0
- package/dist/node/retry-gate.js +53 -0
- package/dist/node/seller-commands.d.ts +12 -0
- package/dist/node/seller-commands.js +207 -0
- package/dist/node/sharing.d.ts +3 -0
- package/dist/node/sharing.js +28 -0
- package/dist/node/shops.d.ts +17 -0
- package/dist/node/shops.js +74 -0
- package/dist/node/signer-mock.d.ts +11 -0
- package/dist/node/signer-mock.js +79 -0
- package/dist/node/signer-wallet.d.ts +8 -0
- package/dist/node/signer-wallet.js +147 -0
- package/dist/node/signing.d.ts +29 -0
- package/dist/node/signing.js +200 -0
- package/dist/node/storage-commands.d.ts +11 -0
- package/dist/node/storage-commands.js +50 -0
- package/dist/node/sync-dir.d.ts +1 -0
- package/dist/node/sync-dir.js +11 -0
- package/dist/node/wallets.d.ts +13 -0
- package/dist/node/wallets.js +146 -0
- package/dist/oneclick/oneclick.d.ts +132 -0
- package/dist/oneclick/oneclick.js +342 -0
- package/dist/pay/backup.d.ts +70 -0
- package/dist/pay/backup.js +402 -0
- package/dist/pay/base.d.ts +135 -0
- package/dist/pay/base.js +393 -0
- package/dist/pay/eip3009.d.ts +59 -0
- package/dist/pay/eip3009.js +72 -0
- package/dist/pay/pay.d.ts +86 -0
- package/dist/pay/pay.js +297 -0
- package/dist/pay/purchase.d.ts +103 -0
- package/dist/pay/purchase.js +20 -0
- package/dist/pay/recover.d.ts +31 -0
- package/dist/pay/recover.js +34 -0
- package/dist/pay/seller.d.ts +130 -0
- package/dist/pay/seller.js +444 -0
- package/dist/pay/settle.d.ts +96 -0
- package/dist/pay/settle.js +308 -0
- package/dist/pay/split.d.ts +115 -0
- package/dist/pay/split.js +256 -0
- package/dist/pay/store-node.d.ts +8 -0
- package/dist/pay/store-node.js +224 -0
- package/dist/pay/store.d.ts +54 -0
- package/dist/pay/store.js +135 -0
- package/dist/pay/x402.d.ts +95 -0
- package/dist/pay/x402.js +136 -0
- package/dist/relay/admission.d.ts +8 -0
- package/dist/relay/admission.js +58 -0
- package/dist/relay/capabilities.d.ts +36 -0
- package/dist/relay/capabilities.js +232 -0
- package/dist/relay/fees.d.ts +17 -0
- package/dist/relay/fees.js +105 -0
- package/dist/relay/index.d.ts +10 -0
- package/dist/relay/index.js +8 -0
- package/dist/relay/keeper-balance.d.ts +17 -0
- package/dist/relay/keeper-balance.js +46 -0
- package/dist/relay/keeper-runner.d.ts +114 -0
- package/dist/relay/keeper-runner.js +703 -0
- package/dist/relay/keeper.d.ts +38 -0
- package/dist/relay/keeper.js +73 -0
- package/dist/relay/ledger.d.ts +155 -0
- package/dist/relay/ledger.js +392 -0
- package/dist/relay/relay-checks.d.ts +3 -0
- package/dist/relay/relay-checks.js +244 -0
- package/dist/relay/relay-reconciliation.d.ts +15 -0
- package/dist/relay/relay-reconciliation.js +293 -0
- package/dist/relay/relay-request.d.ts +6 -0
- package/dist/relay/relay-request.js +139 -0
- package/dist/relay/relay-transactions.d.ts +23 -0
- package/dist/relay/relay-transactions.js +182 -0
- package/dist/relay/relay-types.d.ts +128 -0
- package/dist/relay/relay-types.js +17 -0
- package/dist/relay/relay.d.ts +102 -0
- package/dist/relay/relay.js +426 -0
- package/dist/relay/reservation.d.ts +29 -0
- package/dist/relay/reservation.js +132 -0
- package/dist/relay/sponsorship.d.ts +42 -0
- package/dist/relay/sponsorship.js +184 -0
- package/dist/routes.d.ts +2 -0
- package/dist/routes.js +1 -0
- package/dist/settle-near/codec.d.ts +17 -0
- package/dist/settle-near/codec.js +86 -0
- package/dist/settle-near/identity.d.ts +27 -0
- package/dist/settle-near/identity.js +80 -0
- package/dist/settle-near/purchase.d.ts +40 -0
- package/dist/settle-near/purchase.js +153 -0
- package/dist/settle-near/terms.d.ts +37 -0
- package/dist/settle-near/terms.js +89 -0
- package/dist/shops/discovery.d.ts +95 -0
- package/dist/shops/discovery.js +384 -0
- package/dist/wallet/account-only.d.ts +11 -0
- package/dist/wallet/account-only.js +6 -0
- package/dist/wallet/uncertain.d.ts +8 -0
- package/dist/wallet/uncertain.js +13 -0
- package/dist/wallet/wallet.d.ts +100 -0
- package/dist/wallet/wallet.js +204 -0
- package/package.json +135 -3
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/** Seller-authorized encrypted hosting. No plaintext or file key is sent to the host. */
|
|
2
|
+
import { base64urlnopad } from '@scure/base';
|
|
3
|
+
import { bodyHash, chunkCount, previewLimit, sha256 } from '../envelope/pay2.js';
|
|
4
|
+
import { AnnonError, isAnnonErrorCode, toPublicError } from '../errors.js';
|
|
5
|
+
import { verifyNearMessage } from '../near/nep413.js';
|
|
6
|
+
import { parseReservation } from '../relay/reservation.js';
|
|
7
|
+
import { account, bytes, exactKeys, hex, network } from '../settle-near/codec.js';
|
|
8
|
+
import { identityJson, identityText, parseIdentity, verifyAuthorization, } from '../settle-near/identity.js';
|
|
9
|
+
import { termsHash } from '../settle-near/terms.js';
|
|
10
|
+
export const uploadText = (i) => [
|
|
11
|
+
'Annon: reserve encrypted file hosting',
|
|
12
|
+
`network: ${i.network}`,
|
|
13
|
+
`contract: ${i.contract}`,
|
|
14
|
+
`seller: ${identityText(i.seller)}`,
|
|
15
|
+
`file: ${i.fileId}`,
|
|
16
|
+
`cipher: ${i.cipherHash}`,
|
|
17
|
+
`terms: ${i.termsHash}`,
|
|
18
|
+
`bytes: ${i.byteSize}`,
|
|
19
|
+
`plaintext bytes: ${i.plaintextSize}`,
|
|
20
|
+
`header bytes: ${i.headerSize}`,
|
|
21
|
+
`host: ${i.hostOrigin}`,
|
|
22
|
+
`metadata: ${i.metadataHash}`,
|
|
23
|
+
`nonce: ${i.nonce}`,
|
|
24
|
+
`expires: ${i.expiresAt}`,
|
|
25
|
+
].join('\n');
|
|
26
|
+
const utf8 = new TextEncoder();
|
|
27
|
+
export function uploadOrigin(value) {
|
|
28
|
+
const url = new URL(value);
|
|
29
|
+
if (url.protocol !== 'https:' || url.origin !== value || url.username || url.password)
|
|
30
|
+
throw new AnnonError('bad_request', 'Invalid upload origin.');
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
export function normalizeMetadata(input) {
|
|
34
|
+
exactKeys(input, ['filename', 'title', 'description']);
|
|
35
|
+
const out = {};
|
|
36
|
+
for (const [key, max] of [
|
|
37
|
+
['filename', 255],
|
|
38
|
+
['title', 120],
|
|
39
|
+
['description', 500],
|
|
40
|
+
]) {
|
|
41
|
+
const raw = input[key];
|
|
42
|
+
if (typeof raw !== 'string')
|
|
43
|
+
throw new AnnonError('bad_request', 'Invalid upload metadata.');
|
|
44
|
+
const value = raw.normalize('NFC');
|
|
45
|
+
if (/[\p{Cc}\p{Cs}\p{Cf}\p{Zl}\p{Zp}]/u.test(value) ||
|
|
46
|
+
utf8.encode(value).length > max ||
|
|
47
|
+
(key !== 'description' && !value))
|
|
48
|
+
throw new AnnonError('bad_request', 'Invalid upload metadata.');
|
|
49
|
+
out[key] = value;
|
|
50
|
+
}
|
|
51
|
+
if (/[\\/]/.test(out.filename) || out.filename === '.' || out.filename === '..')
|
|
52
|
+
throw new AnnonError('bad_request', 'Invalid upload filename.');
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
export async function metadataHash(metadata) {
|
|
56
|
+
const m = normalizeMetadata(metadata);
|
|
57
|
+
return hex(await sha256(utf8.encode(JSON.stringify([m.filename, m.title, m.description]))));
|
|
58
|
+
}
|
|
59
|
+
export function parseUploadIntent(input) {
|
|
60
|
+
exactKeys(input, [
|
|
61
|
+
'network',
|
|
62
|
+
'contract',
|
|
63
|
+
'seller',
|
|
64
|
+
'fileId',
|
|
65
|
+
'cipherHash',
|
|
66
|
+
'termsHash',
|
|
67
|
+
'byteSize',
|
|
68
|
+
'plaintextSize',
|
|
69
|
+
'headerSize',
|
|
70
|
+
'hostOrigin',
|
|
71
|
+
'metadataHash',
|
|
72
|
+
'nonce',
|
|
73
|
+
'expiresAt',
|
|
74
|
+
]);
|
|
75
|
+
const i = input;
|
|
76
|
+
network(i.network);
|
|
77
|
+
account(i.contract);
|
|
78
|
+
uploadOrigin(i.hostOrigin);
|
|
79
|
+
const seller = parseIdentity(i.seller);
|
|
80
|
+
if (seller.kind !== i.seller.kind || seller.id !== i.seller.id)
|
|
81
|
+
throw new AnnonError('bad_request', 'Noncanonical seller.');
|
|
82
|
+
for (const k of ['fileId', 'cipherHash', 'termsHash', 'metadataHash', 'nonce'])
|
|
83
|
+
bytes(i[k], 32);
|
|
84
|
+
for (const k of ['byteSize', 'plaintextSize', 'headerSize', 'expiresAt'])
|
|
85
|
+
if (!Number.isSafeInteger(i[k]) || i[k] < 0)
|
|
86
|
+
throw new AnnonError('bad_request', 'Invalid upload size or time.');
|
|
87
|
+
if (i.byteSize <= i.headerSize ||
|
|
88
|
+
i.headerSize < 1 ||
|
|
89
|
+
i.headerSize > 65536 ||
|
|
90
|
+
i.plaintextSize > 50_000_000 ||
|
|
91
|
+
i.byteSize > 50_086_016)
|
|
92
|
+
throw new AnnonError('bad_request', 'Invalid upload size.');
|
|
93
|
+
return { ...structuredClone(i), seller };
|
|
94
|
+
}
|
|
95
|
+
export function parseUploadAuth(input) {
|
|
96
|
+
if (!input || typeof input !== 'object')
|
|
97
|
+
throw new AnnonError('bad_request', 'Invalid upload authorization.');
|
|
98
|
+
if ('kind' in input && input.kind === 'near') {
|
|
99
|
+
exactKeys(input, ['kind', 'accountId', 'publicKey', 'signature']);
|
|
100
|
+
const near = input;
|
|
101
|
+
if (typeof near.accountId !== 'string' ||
|
|
102
|
+
typeof near.publicKey !== 'string' ||
|
|
103
|
+
typeof near.signature !== 'string')
|
|
104
|
+
throw new AnnonError('bad_request', 'Invalid upload authorization.');
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
exactKeys(input, ['kind', 'sig']);
|
|
108
|
+
if (input.kind !== 'evm' && input.kind !== 'solana')
|
|
109
|
+
throw new AnnonError('bad_request', 'Invalid upload authorization.');
|
|
110
|
+
bytes(input.sig, input.kind === 'evm' ? 65 : 64);
|
|
111
|
+
}
|
|
112
|
+
return structuredClone(input);
|
|
113
|
+
}
|
|
114
|
+
export function parseUpload(input) {
|
|
115
|
+
exactKeys(input, ['intent', 'terms', 'manifest', 'metadata', 'auth']);
|
|
116
|
+
const intent = parseUploadIntent(input.intent);
|
|
117
|
+
exactKeys(input.manifest, [
|
|
118
|
+
'version',
|
|
119
|
+
'chunkLog2',
|
|
120
|
+
'metaLen',
|
|
121
|
+
'previewLen',
|
|
122
|
+
'n',
|
|
123
|
+
'keyCommitments',
|
|
124
|
+
'chunkHashes',
|
|
125
|
+
]);
|
|
126
|
+
const m = input.manifest;
|
|
127
|
+
if (![m.chunkLog2, m.metaLen, m.previewLen, m.n].every(Number.isSafeInteger) ||
|
|
128
|
+
m.version !== 2 ||
|
|
129
|
+
m.chunkLog2 < 16 ||
|
|
130
|
+
m.chunkLog2 > 24 ||
|
|
131
|
+
m.metaLen < 256 ||
|
|
132
|
+
m.metaLen > 4096 ||
|
|
133
|
+
m.metaLen % 256 !== 0 ||
|
|
134
|
+
m.previewLen < 0 ||
|
|
135
|
+
m.previewLen > previewLimit(intent.plaintextSize) ||
|
|
136
|
+
m.n < 2 ||
|
|
137
|
+
m.n > 1024 ||
|
|
138
|
+
m.n !== chunkCount(intent.plaintextSize, m.previewLen, m.chunkLog2) ||
|
|
139
|
+
!Array.isArray(m.keyCommitments) ||
|
|
140
|
+
m.keyCommitments.length !== (m.previewLen > 0 ? 2 : 1) ||
|
|
141
|
+
!Array.isArray(m.chunkHashes) ||
|
|
142
|
+
m.chunkHashes.length !== m.n)
|
|
143
|
+
throw new AnnonError('bad_request', 'Invalid upload manifest.');
|
|
144
|
+
for (const h of [...m.keyCommitments, ...m.chunkHashes])
|
|
145
|
+
bytes(h, 32);
|
|
146
|
+
if (intent.headerSize !==
|
|
147
|
+
206 + utf8.encode(intent.contract).length + 32 * (m.keyCommitments.length + m.n) ||
|
|
148
|
+
intent.byteSize - intent.headerSize !== intent.plaintextSize + m.metaLen + 16 * m.n)
|
|
149
|
+
throw new AnnonError('bad_request', 'Invalid upload framing.');
|
|
150
|
+
return {
|
|
151
|
+
intent,
|
|
152
|
+
terms: structuredClone(input.terms),
|
|
153
|
+
manifest: structuredClone(m),
|
|
154
|
+
metadata: normalizeMetadata(input.metadata),
|
|
155
|
+
auth: parseUploadAuth(input.auth),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
export async function verifyUploadAuthorization(a, o) {
|
|
159
|
+
const i = parseUploadIntent(a.intent), auth = parseUploadAuth(a.auth);
|
|
160
|
+
if (i.network !== o.network ||
|
|
161
|
+
i.contract !== o.contract ||
|
|
162
|
+
i.hostOrigin !== o.hostOrigin ||
|
|
163
|
+
i.expiresAt <= o.now ||
|
|
164
|
+
i.expiresAt > o.now + (o.authSeconds ?? 600) ||
|
|
165
|
+
auth.kind !== i.seller.kind)
|
|
166
|
+
throw new AnnonError('bad_request', 'Invalid upload authorization.');
|
|
167
|
+
if (auth.kind === 'near') {
|
|
168
|
+
await verifyNearMessage(i.seller.id, { message: uploadText(i), recipient: i.hostOrigin, nonce: bytes(i.nonce, 32) }, auth, o.nearRpc);
|
|
169
|
+
}
|
|
170
|
+
else if (!verifyAuthorization(i.seller, uploadText(i), auth))
|
|
171
|
+
throw new AnnonError('bad_request', 'Invalid upload signature.');
|
|
172
|
+
}
|
|
173
|
+
export async function verifyUpload(input, o) {
|
|
174
|
+
const r = parseUpload(input), i = r.intent, t = r.terms;
|
|
175
|
+
if (t.network !== i.network ||
|
|
176
|
+
t.contract !== i.contract ||
|
|
177
|
+
t.file_id !== i.fileId ||
|
|
178
|
+
t.cipher_hash !== i.cipherHash ||
|
|
179
|
+
t.pay2_version !== 2 ||
|
|
180
|
+
identityJson(t.seller) !== identityJson(i.seller) ||
|
|
181
|
+
termsHash(t, o.assets) !== i.termsHash ||
|
|
182
|
+
(await metadataHash(r.metadata)) !== i.metadataHash ||
|
|
183
|
+
hex(await bodyHash({
|
|
184
|
+
...r.manifest,
|
|
185
|
+
fileId: bytes(i.fileId, 32),
|
|
186
|
+
commits: r.manifest.keyCommitments.map((h) => bytes(h, 32)),
|
|
187
|
+
hashes: r.manifest.chunkHashes.map((h) => bytes(h, 32)),
|
|
188
|
+
})) !== i.cipherHash)
|
|
189
|
+
throw new AnnonError('bad_request', 'Invalid upload binding.');
|
|
190
|
+
await verifyUploadAuthorization(r, o);
|
|
191
|
+
}
|
|
192
|
+
export function encodeUploadAuthorization(input) {
|
|
193
|
+
const value = base64urlnopad.encode(utf8.encode(JSON.stringify({
|
|
194
|
+
intent: parseUploadIntent(input.intent),
|
|
195
|
+
auth: parseUploadAuth(input.auth),
|
|
196
|
+
})));
|
|
197
|
+
if (value.length > 8192)
|
|
198
|
+
throw new AnnonError('bad_request', 'Upload authorization is too large.');
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
export function decodeUploadAuthorization(value, max = 8192) {
|
|
202
|
+
if (value.length > max || !/^[A-Za-z0-9_-]+$/.test(value))
|
|
203
|
+
throw new AnnonError('bad_request', 'Invalid upload authorization.');
|
|
204
|
+
const a = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(base64urlnopad.decode(value)));
|
|
205
|
+
exactKeys(a, ['intent', 'auth']);
|
|
206
|
+
return { intent: parseUploadIntent(a.intent), auth: parseUploadAuth(a.auth) };
|
|
207
|
+
}
|
|
208
|
+
const failure = (code, retryAfterSeconds) => {
|
|
209
|
+
const safe = toPublicError({ code, retryAfterSeconds });
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
error: Object.assign(new AnnonError(code), safe.retryAfterSeconds === undefined ? {} : { retryAfterSeconds: safe.retryAfterSeconds }),
|
|
213
|
+
};
|
|
214
|
+
};
|
|
215
|
+
function createdResponse(value, origin, expectedId) {
|
|
216
|
+
exactKeys(value, ['uploadId', 'state', 'expiresAt', 'bodyUrl', 'headerUrl']);
|
|
217
|
+
const v = value;
|
|
218
|
+
if (!/^[a-f0-9]{64}$/.test(v.uploadId) ||
|
|
219
|
+
(expectedId !== undefined && v.uploadId !== expectedId) ||
|
|
220
|
+
v.state !== 'receiving' ||
|
|
221
|
+
!Number.isSafeInteger(v.expiresAt) ||
|
|
222
|
+
v.expiresAt < 0 ||
|
|
223
|
+
v.bodyUrl !== `${origin}/v1/uploads/${v.uploadId}/body` ||
|
|
224
|
+
v.headerUrl !== `${origin}/v1/uploads/${v.uploadId}/header`)
|
|
225
|
+
throw new AnnonError('bad_request', 'Invalid upload response.');
|
|
226
|
+
return v;
|
|
227
|
+
}
|
|
228
|
+
function reservedResponse(value, id, intent) {
|
|
229
|
+
exactKeys(value, ['uploadId', 'state', 'reservation']);
|
|
230
|
+
const v = value;
|
|
231
|
+
const r = parseReservation(v.reservation);
|
|
232
|
+
if (v.uploadId !== id ||
|
|
233
|
+
v.state !== 'reserved' ||
|
|
234
|
+
r.uploadId !== id ||
|
|
235
|
+
identityJson(r.seller) !== identityJson(intent.seller))
|
|
236
|
+
throw new AnnonError('bad_request', 'Invalid upload response.');
|
|
237
|
+
for (const key of [
|
|
238
|
+
'network',
|
|
239
|
+
'contract',
|
|
240
|
+
'fileId',
|
|
241
|
+
'termsHash',
|
|
242
|
+
'cipherHash',
|
|
243
|
+
'byteSize',
|
|
244
|
+
'plaintextSize',
|
|
245
|
+
'headerSize',
|
|
246
|
+
'metadataHash',
|
|
247
|
+
'hostOrigin',
|
|
248
|
+
])
|
|
249
|
+
if (r[key] !== intent[key])
|
|
250
|
+
throw new AnnonError('bad_request', 'Invalid upload response.');
|
|
251
|
+
return { ...v, reservation: r };
|
|
252
|
+
}
|
|
253
|
+
function publishedResponse(value, intent) {
|
|
254
|
+
exactKeys(value, ['fileId', 'url', 'header', 'expiresAt']);
|
|
255
|
+
const v = value;
|
|
256
|
+
const url = `${intent.hostOrigin}/f/${intent.fileId.slice(2)}`;
|
|
257
|
+
if (v.fileId !== intent.fileId ||
|
|
258
|
+
v.url !== url ||
|
|
259
|
+
v.header !== `${url}/header` ||
|
|
260
|
+
(v.expiresAt !== null && (!Number.isSafeInteger(v.expiresAt) || v.expiresAt < 0)))
|
|
261
|
+
throw new AnnonError('bad_request', 'Invalid upload response.');
|
|
262
|
+
return v;
|
|
263
|
+
}
|
|
264
|
+
function splitResponse(value) {
|
|
265
|
+
if (value === null)
|
|
266
|
+
return null;
|
|
267
|
+
exactKeys(value, ['state', 'address', 'kind', 'salt', 'txHash']);
|
|
268
|
+
if ((value.state !== 'pending' && value.state !== 'ready') ||
|
|
269
|
+
typeof value.address !== 'string' ||
|
|
270
|
+
!/^0x[0-9a-fA-F]{40}$/.test(value.address) ||
|
|
271
|
+
value.kind !== 'push' ||
|
|
272
|
+
value.salt !== `0x${'00'.repeat(32)}` ||
|
|
273
|
+
(value.txHash !== null &&
|
|
274
|
+
(typeof value.txHash !== 'string' || !/^0x[0-9a-f]{64}$/.test(value.txHash))))
|
|
275
|
+
throw new AnnonError('bad_request');
|
|
276
|
+
return value;
|
|
277
|
+
}
|
|
278
|
+
function statusResponse(value, id, intent) {
|
|
279
|
+
if (!value || typeof value !== 'object' || !('baseSplit' in value))
|
|
280
|
+
throw new AnnonError('bad_request');
|
|
281
|
+
const { baseSplit, ...state } = value;
|
|
282
|
+
const split = splitResponse(baseSplit);
|
|
283
|
+
if ('state' in state && state.state === 'receiving')
|
|
284
|
+
return { ...createdResponse(state, intent.hostOrigin, id), baseSplit: split };
|
|
285
|
+
if ('state' in state && state.state === 'reserved')
|
|
286
|
+
return { ...reservedResponse(state, id, intent), baseSplit: split };
|
|
287
|
+
exactKeys(state, ['uploadId', 'state', 'result']);
|
|
288
|
+
if (state.uploadId !== id || state.state !== 'published')
|
|
289
|
+
throw new AnnonError('bad_request');
|
|
290
|
+
return {
|
|
291
|
+
uploadId: id,
|
|
292
|
+
state: 'published',
|
|
293
|
+
result: publishedResponse(state.result, intent),
|
|
294
|
+
baseSplit: split,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
export class UploadClient {
|
|
298
|
+
origin;
|
|
299
|
+
fetcher;
|
|
300
|
+
constructor(origin, options = {}) {
|
|
301
|
+
this.origin = uploadOrigin(origin);
|
|
302
|
+
this.fetcher = options.fetch ?? fetch;
|
|
303
|
+
}
|
|
304
|
+
async request(path, method, body, authorization, validate) {
|
|
305
|
+
try {
|
|
306
|
+
return await this.requestJson(path, method, body, authorization, validate);
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return failure('operation_failed');
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async requestJson(path, method, body, authorization, validate) {
|
|
313
|
+
const response = await this.fetcher(`${this.origin}${path}`, {
|
|
314
|
+
method,
|
|
315
|
+
credentials: 'omit',
|
|
316
|
+
redirect: 'error',
|
|
317
|
+
signal: AbortSignal.timeout(method === 'PUT' ? 1800000 : 5000),
|
|
318
|
+
headers: {
|
|
319
|
+
...(body === undefined
|
|
320
|
+
? {}
|
|
321
|
+
: { 'Content-Type': method === 'PUT' ? 'application/octet-stream' : 'application/json' }),
|
|
322
|
+
...(authorization
|
|
323
|
+
? { 'Annon-Upload-Authorization': encodeUploadAuthorization(authorization) }
|
|
324
|
+
: {}),
|
|
325
|
+
},
|
|
326
|
+
...(body === undefined ? {} : { body }),
|
|
327
|
+
...(body instanceof ReadableStream ? { duplex: 'half' } : {}),
|
|
328
|
+
});
|
|
329
|
+
if (response.redirected ||
|
|
330
|
+
response.headers.get('content-type')?.split(';')[0]?.trim().toLowerCase() !==
|
|
331
|
+
'application/json') {
|
|
332
|
+
await response.body?.cancel();
|
|
333
|
+
return failure('operation_failed');
|
|
334
|
+
}
|
|
335
|
+
const reader = response.body?.getReader();
|
|
336
|
+
if (!reader)
|
|
337
|
+
return failure('operation_failed');
|
|
338
|
+
const parts = [];
|
|
339
|
+
let size = 0;
|
|
340
|
+
try {
|
|
341
|
+
while (true) {
|
|
342
|
+
const { value, done } = await reader.read();
|
|
343
|
+
if (done)
|
|
344
|
+
break;
|
|
345
|
+
size += value.length;
|
|
346
|
+
if (size > 131072)
|
|
347
|
+
throw new AnnonError('bad_request', 'Upload response is too large.');
|
|
348
|
+
parts.push(value);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
await reader.cancel();
|
|
353
|
+
reader.releaseLock();
|
|
354
|
+
}
|
|
355
|
+
const bytes = new Uint8Array(size);
|
|
356
|
+
let at = 0;
|
|
357
|
+
for (const p of parts) {
|
|
358
|
+
bytes.set(p, at);
|
|
359
|
+
at += p.length;
|
|
360
|
+
}
|
|
361
|
+
const value = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
362
|
+
if (isAnnonErrorCode(value?.error))
|
|
363
|
+
return failure(value.error, value.retryAfterSeconds);
|
|
364
|
+
if (response.ok) {
|
|
365
|
+
try {
|
|
366
|
+
if (!validate)
|
|
367
|
+
throw new AnnonError('bad_request');
|
|
368
|
+
return { ok: true, value: validate(value, response.status) };
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
return failure('operation_failed');
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const code = isAnnonErrorCode(value?.error) ? value.error : 'operation_failed';
|
|
375
|
+
return failure(code, code === 'operation_failed' ? undefined : value.retryAfterSeconds);
|
|
376
|
+
}
|
|
377
|
+
path(id) {
|
|
378
|
+
if (!/^[a-f0-9]{64}$/.test(id))
|
|
379
|
+
throw new AnnonError('bad_request', 'Invalid upload ID.');
|
|
380
|
+
return `/v1/uploads/${id}`;
|
|
381
|
+
}
|
|
382
|
+
create(request) {
|
|
383
|
+
return this.request('/v1/uploads', 'POST', JSON.stringify(parseUpload(request)), undefined, (value) => value && typeof value === 'object' && 'baseSplit' in value
|
|
384
|
+
? statusResponse(value, value.uploadId, request.intent)
|
|
385
|
+
: createdResponse(value, this.origin));
|
|
386
|
+
}
|
|
387
|
+
async body(id, auth, body) {
|
|
388
|
+
return this.request(`${this.path(id)}/body`, 'PUT', body, auth, (value) => reservedResponse(value, id, auth.intent));
|
|
389
|
+
}
|
|
390
|
+
async header(id, auth, header) {
|
|
391
|
+
return this.request(`${this.path(id)}/header`, 'PUT', header, auth, (value) => publishedResponse(value, auth.intent));
|
|
392
|
+
}
|
|
393
|
+
async status(id, auth) {
|
|
394
|
+
return this.request(this.path(id), 'GET', undefined, auth, (value) => statusResponse(value, id, auth.intent));
|
|
395
|
+
}
|
|
396
|
+
async baseSplit(id, auth, input) {
|
|
397
|
+
exactKeys(input, ['kind', 'salt']);
|
|
398
|
+
if (input.kind !== 'push' || input.salt !== `0x${'00'.repeat(32)}`)
|
|
399
|
+
throw new AnnonError('bad_request');
|
|
400
|
+
return this.request(`${this.path(id)}/base-split`, 'POST', JSON.stringify(input), auth, (value, status) => {
|
|
401
|
+
exactKeys(value, ['baseSplit']);
|
|
402
|
+
const baseSplit = splitResponse(value.baseSplit);
|
|
403
|
+
if (!baseSplit || status !== (baseSplit.state === 'pending' ? 202 : 200))
|
|
404
|
+
throw new AnnonError('bad_request');
|
|
405
|
+
return { baseSplit };
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { secp256k1 } from '@noble/curves/secp256k1.js';
|
|
2
|
+
import { keccak_256 } from '@noble/hashes/sha3.js';
|
|
3
|
+
import { hexDigits as toHex } from '../bytes.js';
|
|
4
|
+
const utf8 = new TextEncoder();
|
|
5
|
+
const concat = (...parts) => Uint8Array.from(parts.flatMap((p) => Array.from(p)));
|
|
6
|
+
/**
|
|
7
|
+
* Whether `sig` (r ‖ s ‖ v, low s, v 27 or 28) is `address` signing `text` with EIP-191.
|
|
8
|
+
*/
|
|
9
|
+
export function signedBy(text, sig, address) {
|
|
10
|
+
return signerOf(text, sig) === address.toLowerCase();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The address that signed `text` with EIP-191 (r ‖ s ‖ v, low s, v 27 or 28), lowercase 0x, or
|
|
14
|
+
* null for a signature that recovers to nobody.
|
|
15
|
+
*/
|
|
16
|
+
function signerOf(text, sig) {
|
|
17
|
+
if (sig.length !== 65 || (sig[64] !== 27 && sig[64] !== 28))
|
|
18
|
+
return null;
|
|
19
|
+
const s = BigInt(`0x${toHex(sig.subarray(32, 64))}`);
|
|
20
|
+
if (s > secp256k1.Point.Fn.ORDER / 2n)
|
|
21
|
+
return null;
|
|
22
|
+
const t = utf8.encode(text);
|
|
23
|
+
const digest = keccak_256(concat(utf8.encode(`\x19Ethereum Signed Message:\n${t.length}`), t));
|
|
24
|
+
try {
|
|
25
|
+
const recovered = concat(new Uint8Array([(sig[64] ?? 27) - 27]), sig.subarray(0, 64));
|
|
26
|
+
const pub = secp256k1.Point.fromBytes(secp256k1.recoverPublicKey(recovered, digest, { prehash: false }));
|
|
27
|
+
return `0x${toHex(keccak_256(pub.toBytes(false).subarray(1)).subarray(12))}`;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { AnnonError } from '../errors.js';
|
|
2
|
+
import { type Identity } from '../settle-near/identity.js';
|
|
3
|
+
export type Bytes = Uint8Array<ArrayBuffer>;
|
|
4
|
+
export type Pay2ErrorCode = 'bad_magic' | 'unsupported_version' | 'bad_chunk_size' | 'header_too_large' | 'bad_header' | 'bad_stanza' | 'file_id_mismatch' | 'cipher_hash_mismatch' | 'seller_signature_invalid' | 'bad_body_length' | 'chunk_hash_mismatch' | 'no_usable_stanza' | 'key_unwrap_failed' | 'header_mac_invalid' | 'chunk_invalid' | 'meta_invalid' | 'preview_invalid';
|
|
5
|
+
export declare class Pay2Error extends AnnonError {
|
|
6
|
+
readonly code: Pay2ErrorCode;
|
|
7
|
+
constructor(code: Pay2ErrorCode, message?: string);
|
|
8
|
+
}
|
|
9
|
+
export interface FileMeta {
|
|
10
|
+
name: string;
|
|
11
|
+
mime: string;
|
|
12
|
+
size: number;
|
|
13
|
+
}
|
|
14
|
+
/** What the contract holds for the file (`get_file`), which the bytes are checked against. */
|
|
15
|
+
export interface Listing {
|
|
16
|
+
fileId: string | Uint8Array;
|
|
17
|
+
cipherHash: string | Uint8Array;
|
|
18
|
+
/** Registered seller; an EVM address is accepted as API shorthand. */
|
|
19
|
+
seller: string | Identity;
|
|
20
|
+
/** NEAR v2 only: SHA256(header without its 65-byte trailer), read at finality. */
|
|
21
|
+
publishedHeaderHash?: string | Uint8Array;
|
|
22
|
+
/** Registered version, always 2. */
|
|
23
|
+
pay2Version?: 2;
|
|
24
|
+
}
|
|
25
|
+
export interface PreviewPackage {
|
|
26
|
+
v: 1;
|
|
27
|
+
header: string;
|
|
28
|
+
chunks: {
|
|
29
|
+
index: number;
|
|
30
|
+
key: string;
|
|
31
|
+
data: string;
|
|
32
|
+
}[];
|
|
33
|
+
}
|
|
34
|
+
export declare const MAGIC: number[];
|
|
35
|
+
export declare const FIXED = 54;
|
|
36
|
+
export declare const TAG = 16;
|
|
37
|
+
export declare const MAX_HEADER = 65536;
|
|
38
|
+
export declare const MAX_CHUNKS = 1024;
|
|
39
|
+
export declare const MAX_META = 4096;
|
|
40
|
+
export declare const MAC = 32;
|
|
41
|
+
export declare const SIG = 65;
|
|
42
|
+
export declare const NEAR_CKD = "near-ckd/v1";
|
|
43
|
+
export declare function concat(...parts: Uint8Array[]): Bytes;
|
|
44
|
+
export declare function int(value: number, bytes: 1 | 2 | 4 | 8): Bytes;
|
|
45
|
+
export declare const equal: (a: Uint8Array, b: Uint8Array) => boolean;
|
|
46
|
+
export declare const copy: (b: Uint8Array) => Bytes;
|
|
47
|
+
export declare function sha256(...parts: Uint8Array[]): Promise<Bytes>;
|
|
48
|
+
/** Derives 32 bytes. `ikm` is raw bytes or an HKDF key, so an unwrapped `K` never leaves WebCrypto. */
|
|
49
|
+
declare function hkdf(ikm: Uint8Array | CryptoKey, salt: Uint8Array, info: Uint8Array | string): Promise<Uint8Array<ArrayBuffer>>;
|
|
50
|
+
export declare const chunkKey: (K: Uint8Array | CryptoKey, fileId: Uint8Array, i: number) => Promise<Uint8Array<ArrayBuffer>>;
|
|
51
|
+
/** Spec 2: binds a revealable chunk key, since AES-GCM alone doesn't commit to its key. */
|
|
52
|
+
export declare const commitment: (k: Uint8Array) => Promise<Bytes>;
|
|
53
|
+
declare function gcm(mode: 'encrypt' | 'decrypt', key: Uint8Array, last: boolean, data: Uint8Array): Promise<Uint8Array<ArrayBuffer>>;
|
|
54
|
+
export { gcm, hkdf };
|
|
55
|
+
export declare const wrapKeyFor: (ckdSecret: Uint8Array, fileId: Uint8Array, usage: KeyUsage) => Promise<CryptoKey>;
|
|
56
|
+
/** Spec 3: binds the layout fields known before `register`, the commitments and the hash list. */
|
|
57
|
+
export declare const bodyHash: (h: Pick<Header, "chunkLog2" | "fileId" | "metaLen" | "previewLen" | "n" | "commits" | "hashes"> & {
|
|
58
|
+
version?: 2;
|
|
59
|
+
}) => Promise<Bytes>;
|
|
60
|
+
export { signedBy } from './evm.js';
|
|
61
|
+
interface Header {
|
|
62
|
+
version: 2;
|
|
63
|
+
authKind: 0 | 1 | 2;
|
|
64
|
+
chunkLog2: number;
|
|
65
|
+
fileId: Bytes;
|
|
66
|
+
metaLen: number;
|
|
67
|
+
previewLen: number;
|
|
68
|
+
n: number;
|
|
69
|
+
commits: Bytes[];
|
|
70
|
+
hashes: Bytes[];
|
|
71
|
+
prefixLen: number;
|
|
72
|
+
headerLen: number;
|
|
73
|
+
stanzas: {
|
|
74
|
+
type: string;
|
|
75
|
+
body: Bytes;
|
|
76
|
+
}[];
|
|
77
|
+
}
|
|
78
|
+
/** Spec 6, step 1. With `prefixOnly`, stops after the hash list (a preview package). */
|
|
79
|
+
export declare function parseHeader(b: Bytes, prefixOnly?: boolean): Header;
|
|
80
|
+
/** Spec 5.1: `u8 len ‖ contractId ‖ wrapped K (40)`. */
|
|
81
|
+
export declare function nearCkd(body: Bytes): {
|
|
82
|
+
contractId: string;
|
|
83
|
+
wrapped: Bytes;
|
|
84
|
+
} | undefined;
|
|
85
|
+
/** Spec 3: the chunk count a file of `size` bytes must have. */
|
|
86
|
+
export declare const chunkCount: (size: number, previewLen: number, chunkLog2: number) => number;
|
|
87
|
+
/** Spec 3.1, and the checks against the header that need only the metadata. */
|
|
88
|
+
export declare function parseMeta(piece: Bytes, h: Header): FileMeta;
|
|
89
|
+
export interface Inspected {
|
|
90
|
+
fileId: Bytes;
|
|
91
|
+
/** Whether a `near-ckd/v1` stanza names the trusted contract; `open` needs one. */
|
|
92
|
+
usable: boolean;
|
|
93
|
+
/** The header, signature included: the bytes `info.header` serves (x402-annon.md section 7). */
|
|
94
|
+
header: Bytes;
|
|
95
|
+
/** `SHA256(header)`, which the download challenge names. */
|
|
96
|
+
headerHash: Bytes;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Spec 6, steps 1 to 4: needs no key, so a client can run it before paying when the bytes are
|
|
100
|
+
* public. Throws `Pay2Error`.
|
|
101
|
+
*/
|
|
102
|
+
export declare function inspect(file: Uint8Array, listing: Listing, contractId: string): Promise<Inspected>;
|
|
103
|
+
/**
|
|
104
|
+
* Spec 6, steps 1 to 3, on the header alone: what a client checks before paying when only the
|
|
105
|
+
* header is public (x402-annon.md section 7). `header` must be exactly the header, with no body bytes.
|
|
106
|
+
* It proves which bytes the buyer pays for, not that a host holds them. Throws `Pay2Error`.
|
|
107
|
+
*/
|
|
108
|
+
export declare function inspectHeader(header: Uint8Array, listing: Listing, contractId: string): Promise<Inspected>;
|
|
109
|
+
/** Spec 6, steps 1 to 3. */
|
|
110
|
+
export declare function checkSigned(b: Bytes, listing: Listing, contractId: string): Promise<Header>;
|
|
111
|
+
export interface OpenOptions {
|
|
112
|
+
/** The contract's registration of this file. */
|
|
113
|
+
listing: Listing;
|
|
114
|
+
/** The Annon contract the client trusts. Never taken from the file or a link. */
|
|
115
|
+
contractId: string;
|
|
116
|
+
/** Returns the 48-byte CKD point for this file; called only after steps 1 to 5 pass. */
|
|
117
|
+
ckdSecret: (fileId: Uint8Array) => Promise<Uint8Array>;
|
|
118
|
+
}
|
|
119
|
+
/** Spec 6, all steps. Returns nothing unless every check passes. */
|
|
120
|
+
export declare function open(file: Uint8Array, opts: OpenOptions): Promise<{
|
|
121
|
+
meta: FileMeta;
|
|
122
|
+
data: Bytes;
|
|
123
|
+
}>;
|
|
124
|
+
export interface SealOptions {
|
|
125
|
+
/** Select the registered seller authentication kind before sealing. Version is always 2. */
|
|
126
|
+
version?: 2;
|
|
127
|
+
authKind?: 0 | 1 | 2;
|
|
128
|
+
fileId: Uint8Array;
|
|
129
|
+
name: string;
|
|
130
|
+
mime: string;
|
|
131
|
+
/** Bytes of the file shown as a verified preview; 0 (default) means none. Spec 3 caps it. */
|
|
132
|
+
previewLen?: number;
|
|
133
|
+
/** Spec 3: 22 (4 MiB) unless the file needs more than 1024 chunks. */
|
|
134
|
+
chunkLog2?: number;
|
|
135
|
+
}
|
|
136
|
+
export interface FinishOptions {
|
|
137
|
+
contractId: string;
|
|
138
|
+
ckdSecret: Uint8Array;
|
|
139
|
+
seller: string | Identity;
|
|
140
|
+
/** EVM: EIP-191; Solana: Wallet Standard result including the exact signed message. */
|
|
141
|
+
sign?: (text: string) => Promise<string | Uint8Array | {
|
|
142
|
+
signature: Uint8Array;
|
|
143
|
+
signedMessage: Uint8Array;
|
|
144
|
+
}>;
|
|
145
|
+
}
|
|
146
|
+
/** @internal Shared by buffered and streaming encoders. */
|
|
147
|
+
export declare function sealAuth(opts: SealOptions): {
|
|
148
|
+
version: 2;
|
|
149
|
+
authKind: 0 | 1 | 2;
|
|
150
|
+
};
|
|
151
|
+
/** @internal Build and check the fixed-width authentication trailer. */
|
|
152
|
+
export declare function finishTrailer(version: 2, authKind: 0 | 1 | 2, opts: FinishOptions, fileId: Uint8Array, headerHash: Uint8Array): Promise<Uint8Array>;
|
|
153
|
+
export interface Sealed {
|
|
154
|
+
fileId: Bytes;
|
|
155
|
+
/** Goes into `register` as `cipher_hash`. */
|
|
156
|
+
cipherHash: Bytes;
|
|
157
|
+
/**
|
|
158
|
+
* Once `register` has returned the CKD point: writes the `near-ckd/v1` stanza and the MAC, then
|
|
159
|
+
* asks the seller's wallet to sign `publishText` (EIP-191) and checks the signature.
|
|
160
|
+
*/
|
|
161
|
+
finish(opts: FinishOptions): Promise<Bytes>;
|
|
162
|
+
/**
|
|
163
|
+
* The preview package (spec 7), after `finish`. Publishes those chunks' keys: only when the
|
|
164
|
+
* seller asks.
|
|
165
|
+
*/
|
|
166
|
+
preview(show: {
|
|
167
|
+
meta?: boolean;
|
|
168
|
+
data?: boolean;
|
|
169
|
+
}): Promise<PreviewPackage>;
|
|
170
|
+
}
|
|
171
|
+
/** Encrypts a file under a fresh random key. */
|
|
172
|
+
export declare function seal(data: Uint8Array, opts: SealOptions): Promise<Sealed>;
|
|
173
|
+
/** Encrypts with a caller-supplied fresh key, including deterministic replay. Never reuse it for another file. */
|
|
174
|
+
export declare function sealWithKey(K: Uint8Array, data: Uint8Array, opts: SealOptions): Promise<Sealed>;
|
|
175
|
+
/** Spec 3: at most 64 KiB and a tenth of the file. */
|
|
176
|
+
export declare const previewLimit: (size: number) => number;
|