Claude talks to you while it works: spoken narration in a browser page, and a voice side chat that answers questions and passes instructions without…

A Claude Code mod that lets Claude talk to you while it works, as on a call.
say tool and rules for when to use it: the plan, a changed hypothesis, a finding, a real fork in the road, the outcome. Routine reads and commands stay silent. /narrator level off|quiet|normal|chatty sets how talkative it is./plugin marketplace add adayarabov/marketplace
/plugin install narrator@adayarabov
Or from this repository alone:
/plugin marketplace add adayarabov/narrator
/plugin install narrator@narrator
Then start a session, open the link from the toast (or run /narrator link), and press Enable sound.
Everything is one command, /narrator (Claude Code has its own built-in /voice):
| Command | ||||
|---|---|---|---|---|
/narrator or /narrator status | Whether it is on, voice level, side chat, relay, page link and side-chat spend | |||
/narrator on / /narrator off | Turn narrator on or off as a whole: speech, side chat and the page channel. The page link stays the same | |||
/narrator link | The page link for this session | |||
| `/narrator level [off\ | quiet\ | normal\ | chatty]` | How much Claude speaks |
/narrator usage | Tokens and cost the side-chat forks have spent in this session |
#, which browsers never send to a server. The relay stores and forwards ciphertext.By default the mod uses https://narrator.trq.one. To use your own relay, set the serviceUrl option (/plugin configure narrator@adayarabov).
Each side-chat message is one fork of the session: one request over the session's context, read from the prompt cache. With a large context that is about $0.10 per message on Claude Opus; /narrator usage shows the real numbers.
hooks/register.ts 388 lines1import type { EngineInterface, Register } from 'claude-code'
2
3import {
4 authHeaders,
5 channelUrl,
6 createCredentials,
7 isCredentials,
8 openSealed,
9 pageUrl,
10 sealMessage,
11 type ChannelCredentials,
12 type OutboundMessage,
13 type Priority,
14} from './channel'
15import {
16 addUsage,
17 buildForkPrompt,
18 describeUsage,
19 emptyState,
20 emptyUsage,
21 isForkUsage,
22 isSideChatState,
23 noteForMainThread,
24 parseForkReply,
25 parseInbound,
26 remember,
27 type Inbound,
28 type SideChatState,
29} from './sidechat'
30
31type Level = 'off' | 'quiet' | 'normal' | 'chatty'
32
33const LEVELS: readonly Level[] = ['off', 'quiet', 'normal', 'chatty']
34const DEFAULT_LEVEL: Level = 'normal'
35const LEVEL_KEY = 'level'
36const MAX_UTTERANCE = 400
37const SAY_TOOL = 'mcp__narrator__say'
38const DEFAULT_SERVICE_URL = 'https://narrator.trq.one'
39const INBOX_POLL_MS = 1500
40const STOP_QUESTION = 'Прервать текущую задачу?'
41
42// The running turn's id, for a confirmed stop; a reload happens between turns, so it is not lost mid-turn.
43let runningTurnId: string | undefined
44let isPolling = false
45let pollTimer: { cancel: () => void } | undefined
46
47// Turned on and off with /narrator; kept across sessions.
48const ENABLED_KEY = 'enabled'
49
50const isEnabled = async ($: EngineInterface): Promise<boolean> => (await $.store.get(ENABLED_KEY)) !== false
51
52const SPOKEN_NOTIFICATIONS: Record<string, string> = {
53 permission_prompt: 'Мне нужно твоё разрешение.',
54 elicitation_dialog: 'У меня к тебе вопрос.',
55}
56
57const BASE_RULES = `# Speaking aloud
58
59The developer listens to you through a voice channel while you work, like a colleague on a call.
60Call the \`${SAY_TOOL}\` tool to say something aloud. It returns at once; keep working.
61
62How to speak:
63- In the language the developer writes in, first person, conversational, one or two short sentences.
64- Never read out code, file paths, line numbers, commands, or long identifiers: say what they mean.
65- Speech is a side channel. Your written answer stays as complete as it would be without it.`
66
67const LEVEL_RULES: Record<Exclude<Level, 'off'>, string> = {
68 quiet: `When to speak: only to say you are blocked or need a decision, and once at the end with the outcome in a sentence. Nothing else.`,
69 normal: `When to speak:
70- At the start: the plan in one sentence.
71- When a hypothesis changes, something surprising turns up, or you find the cause.
72- At a real fork where you pick one way over another, and why.
73- At the end: the outcome in one or two sentences.
74Stay silent for routine reads, searches, and commands, and when nothing has changed since you last spoke. Several minutes of silence during routine work is fine.`,
75 chatty: `When to speak: think aloud. Say what you are looking at and why, what you expect, what you just learned, and the outcome at the end. Still skip trivial steps and never repeat yourself.`,
76}
77
78const isLevel = (value: unknown): value is Level =>
79 typeof value === 'string' && (LEVELS as readonly string[]).includes(value)
80
81const readLevel = async ($: EngineInterface): Promise<Level> => {
82 const stored = await $.store.get(LEVEL_KEY)
83 return isLevel(stored) ? stored : DEFAULT_LEVEL
84}
85
86const channelStoreKey = async ($: EngineInterface): Promise<string> => `channel:${await $.session.id()}`
87
88const loadChannel = async ($: EngineInterface): Promise<ChannelCredentials> => {
89 const key = await channelStoreKey($)
90 const stored = await $.store.get(key)
91 if (isCredentials(stored)) return stored
92
93 const created = createCredentials()
94 await $.store.set(key, created)
95 return created
96}
97
98const openChannel = async ($: EngineInterface, serviceUrl: string, channel: ChannelCredentials) => {
99 const response = await $.http.fetch(channelUrl(serviceUrl, channel), { method: 'PUT', headers: authHeaders(channel) })
100 if (!response.ok) throw new Error(`narrator relay refused to open the channel: HTTP ${response.status}`)
101}
102
103// Closes the relay channel but keeps its key, so turning narrator back on reuses the same page link.
104const releaseChannel = async ($: EngineInterface, serviceUrl: string) => {
105 const channel = await loadChannel($)
106 await $.http.fetch(channelUrl(serviceUrl, channel), { method: 'DELETE', headers: authHeaders(channel) })
107}
108
109const closeChannel = async ($: EngineInterface, serviceUrl: string) => {
110 await releaseChannel($, serviceUrl)
111 await $.store.delete(await channelStoreKey($))
112}
113
114const publish = async ($: EngineInterface, serviceUrl: string, message: OutboundMessage) => {
115 const channel = await loadChannel($)
116 const post = () =>
117 $.http.fetch(channelUrl(serviceUrl, channel, '/messages'), {
118 method: 'POST',
119 headers: authHeaders(channel),
120 body: JSON.stringify(sealMessage(channel, message, Date.now())),
121 })
122 let response = await post()
123 if (response.status === 404) {
124 // The relay keeps channels in memory; after its restart the channel is reopened under the same key.
125 await openChannel($, serviceUrl, channel)
126 response = await post()
127 }
128 if (!response.ok) throw new Error(`narrator relay refused the message: HTTP ${response.status}`)
129}
130
131const enqueue = async ($: EngineInterface, serviceUrl: string, text: string, priority: Priority) => {
132 const utterance = text.trim().slice(0, MAX_UTTERANCE)
133 if (utterance === '' || !(await isEnabled($)) || (await readLevel($)) === 'off') return
134
135 await publish($, serviceUrl, { text: utterance, priority, kind: 'narration' })
136}
137
138const sideChatKey = async ($: EngineInterface): Promise<string> => `sidechat:${await $.session.id()}`
139
140const loadSideChat = async ($: EngineInterface): Promise<SideChatState> => {
141 const stored = await $.store.get(await sideChatKey($))
142 return isSideChatState(stored) ? stored : emptyState()
143}
144
145const saveSideChat = async ($: EngineInterface, state: SideChatState) => {
146 await $.store.set(await sideChatKey($), state)
147}
148
149const usageKey = async ($: EngineInterface): Promise<string> => `usage:${await $.session.id()}`
150
151const recordForkUsage = async ($: EngineInterface, usage: Record<string, unknown> | undefined) => {
152 const key = await usageKey($)
153 const stored = await $.store.get(key)
154 await $.store.set(key, addUsage(isForkUsage(stored) ? stored : emptyUsage(), usage))
155}
156
157// An instruction reaches the main thread before its next step, or starts a turn when idle.
158const deliverNote = async ($: EngineInterface, note: string) => {
159 const text = noteForMainThread(note)
160 if (runningTurnId === undefined) {
161 void $.prompt.submit({ text })
162 return
163 }
164 await $.session.append({ message: { type: 'user', content: [{ type: 'text', text }] } })
165}
166
167const answer = async (
168 $: EngineInterface,
169 serviceUrl: string,
170 state: SideChatState,
171 message: Extract<Inbound, { kind: 'say' }>,
172): Promise<SideChatState> => {
173 await publish($, serviceUrl, { text: message.text, priority: 'normal', kind: 'heard', ref: message.id })
174 const forked = await $.model.fork({ prompt: buildForkPrompt(state.history, message.text) })
175 if ('usage' in forked) await recordForkUsage($, forked.usage as Record<string, unknown> | undefined)
176 if (!forked.isAnswered) {
177 const reply = `Не смог ответить: ${forked.reason}.`
178 await publish($, serviceUrl, { text: reply, priority: 'normal', kind: 'reply', ref: message.id })
179 return state
180 }
181
182 const decision = parseForkReply(forked.text)
183 const history = remember(state.history, { role: 'developer', text: message.text }, { role: 'assistant', text: decision.reply })
184 if (decision.action === 'stop') {
185 const text = `${decision.reply} ${STOP_QUESTION}`.trim()
186 await publish($, serviceUrl, { text, priority: 'urgent', kind: 'confirm', ref: message.id })
187 return { ...state, history, pendingStops: { ...state.pendingStops, [message.id]: decision.note } }
188 }
189 if (decision.action === 'note') await deliverNote($, decision.note)
190 await publish($, serviceUrl, { text: decision.reply, priority: 'normal', kind: 'reply', ref: message.id })
191 return { ...state, history }
192}
193
194const confirmStop = async (
195 $: EngineInterface,
196 serviceUrl: string,
197 state: SideChatState,
198 message: Extract<Inbound, { kind: 'confirm' }>,
199): Promise<SideChatState> => {
200 const note = state.pendingStops[message.ref]
201 if (note === undefined) return state
202 const { [message.ref]: _, ...pendingStops } = state.pendingStops
203 if (!message.isConfirmed) {
204 await publish($, serviceUrl, { text: 'Хорошо, продолжаю.', priority: 'normal', kind: 'reply', ref: message.ref })
205 return { ...state, pendingStops }
206 }
207 if (runningTurnId !== undefined) await $.turn.abort({ turnId: runningTurnId })
208 void $.prompt.submit({ text: noteForMainThread(note) })
209 await publish($, serviceUrl, { text: 'Прервал, переключаюсь.', priority: 'urgent', kind: 'reply', ref: message.ref })
210 return { ...state, pendingStops }
211}
212
213const pollInbox = async ($: EngineInterface, serviceUrl: string) => {
214 const channel = await loadChannel($)
215 let state = await loadSideChat($)
216 const response = await $.http.fetch(channelUrl(serviceUrl, channel, `/inbox?after=${state.after}`), {
217 headers: authHeaders(channel),
218 })
219 if (response.status === 404) {
220 await openChannel($, serviceUrl, channel)
221 return
222 }
223 if (!response.ok) return
224
225 const items = (JSON.parse(response.text) as { items?: { seq: number; nonce: string; ciphertext: string }[] }).items ?? []
226 for (const item of items) {
227 state = { ...state, after: Math.max(state.after, item.seq) }
228 const message = parseInbound(openSealed(channel, item.nonce, item.ciphertext), Date.now())
229 if (message === null) continue
230 state = message.kind === 'say'
231 ? await answer($, serviceUrl, state, message)
232 : await confirmStop($, serviceUrl, state, message)
233 await saveSideChat($, state)
234 }
235 await saveSideChat($, state)
236}
237
238// Opens the channel, shows the page link and starts reading the side chat.
239const startNarrator = async ($: EngineInterface, serviceUrl: string): Promise<string> => {
240 const channel = await loadChannel($)
241 await openChannel($, serviceUrl, channel)
242 pollTimer ??= $.clock.every(INBOX_POLL_MS, async () => {
243 if (isPolling) return
244 isPolling = true
245 try {
246 await pollInbox($, serviceUrl)
247 } catch (error) {
248 $.ui.log(`narrator side chat: ${String(error)}`)
249 } finally {
250 isPolling = false
251 }
252 })
253 return pageUrl(serviceUrl, channel)
254}
255
256const stopNarrator = async ($: EngineInterface, serviceUrl: string) => {
257 pollTimer?.cancel()
258 pollTimer = undefined
259 await releaseChannel($, serviceUrl)
260}
261
262export const register: Register = (on, options) => {
263 const configured = options.serviceUrl
264 const serviceUrl = (typeof configured === 'string' && configured !== '' ? configured : DEFAULT_SERVICE_URL).replace(/\/+$/, '')
265
266 on('session.start', async ($, e, next) => {
267 await $.tool.register({
268 name: 'say',
269 description:
270 'Say one or two short conversational sentences aloud to the developer, who listens while you work. ' +
271 'Returns immediately. Follow the "Speaking aloud" rules for when to use it.',
272 inputSchema: {
273 type: 'object',
274 properties: { text: { type: 'string', description: 'What to say: plain speech, no code or paths.' } },
275 required: ['text'],
276 },
277 })
278 // One command for everything: Claude Code has a built-in /voice, and a refused
279 // registration must not stop the channel from opening.
280 try {
281 await $.command.register({
282 name: 'narrator',
283 description: 'Narrator: on, off, status, link, usage, or level off|quiet|normal|chatty',
284 argumentHint: '[on|off|status|link|usage|level <off|quiet|normal|chatty>]',
285 immediate: true,
286 })
287 } catch (error) {
288 $.ui.log(`narrator: /narrator is unavailable: ${String(error)}`)
289 }
290 if (await isEnabled($)) {
291 try {
292 $.ui.toast(`Narrator: ${await startNarrator($, serviceUrl)}`)
293 } catch (error) {
294 $.ui.toast(`Narrator relay is unreachable, speech is off: ${String(error)}`)
295 }
296 }
297 return next(e)
298 })
299
300 on('turn.start', async ($, e, next) => {
301 runningTurnId = e.turnId
302 return next(e)
303 })
304
305 on('turn.complete', async ($, e, next) => {
306 runningTurnId = undefined
307 return next(e)
308 })
309
310 on('command.run', { command: 'narrator' }, async ($, e) => {
311 const [action = 'status', argument = ''] = e.args.trim().toLowerCase().split(/\s+/).filter(Boolean)
312 if (action === 'on') {
313 await $.store.set(ENABLED_KEY, true)
314 try {
315 const link = await startNarrator($, serviceUrl)
316 return { text: `Narrator is on: speech, side chat and the page channel.\nPage: ${link}` }
317 } catch (error) {
318 return { text: `Narrator is on, but the relay is unreachable: ${String(error)}` }
319 }
320 }
321 if (action === 'off') {
322 await $.store.set(ENABLED_KEY, false)
323 await stopNarrator($, serviceUrl).catch(() => undefined)
324 return { text: 'Narrator is off: no speech, the side chat is not read and the page channel is closed. /narrator on brings it back with the same link.' }
325 }
326 if (action === 'link') {
327 return { text: `Open this page to hear the session (the key after # never reaches the server):\n${pageUrl(serviceUrl, await loadChannel($))}` }
328 }
329 if (action === 'usage') {
330 const stored = await $.store.get(await usageKey($))
331 return { text: describeUsage(isForkUsage(stored) ? stored : emptyUsage(), await $.session.model()) }
332 }
333 if (action === 'level') {
334 if (argument === '') return { text: `Voice level: ${await readLevel($)}. Options: ${LEVELS.join(', ')}.` }
335 if (!isLevel(argument)) return { text: `Unknown level "${argument}". Options: ${LEVELS.join(', ')}.` }
336 await $.store.set(LEVEL_KEY, argument)
337 return { text: `Voice level set to ${argument}; it applies from the next request.` }
338 }
339 if (action !== 'status') {
340 return { text: `Unknown argument "${action}". Use /narrator on, off, status, link, usage or level <off|quiet|normal|chatty>.` }
341 }
342
343 const usage = await $.store.get(await usageKey($))
344 const lines = [
345 `Narrator: ${(await isEnabled($)) ? 'on' : 'off'}`,
346 `Voice level: ${await readLevel($)}`,
347 `Side chat: ${pollTimer === undefined ? 'not read' : 'read every 1.5 s'}`,
348 `Relay: ${serviceUrl}`,
349 `Page: ${pageUrl(serviceUrl, await loadChannel($))}`,
350 describeUsage(isForkUsage(usage) ? usage : emptyUsage(), await $.session.model()),
351 ]
352 return { text: lines.join('\n') }
353 })
354
355 on('session.end', async ($, e, next) => {
356 await closeChannel($, serviceUrl)
357 return next(e)
358 }).catch(($, e, next) => next(e))
359
360 on('tool.call', { tool: SAY_TOOL }, async ($, e) => {
361 const input = e as { text?: unknown; agentId?: unknown }
362 // Only the main thread narrates. A side-chat fork (or a subagent) calling say would voice
363 // its answer twice: once here and once as its reply.
364 if (input.agentId !== undefined) return { deny: 'Only the main session speaks aloud; answer in text.' }
365 const text = typeof input.text === 'string' ? input.text : ''
366 if (text.trim() === '') return { deny: 'Nothing to say: pass non-empty text.' }
367 if (!(await isEnabled($))) return { result: 'Narrator is off; nothing was spoken. Continue without speaking.' }
368
369 await enqueue($, serviceUrl, text, 'normal')
370 return { result: 'Queued for speech.' }
371 }).catch(() => ({ deny: 'Speech is unavailable right now; continue without it.' }))
372
373 on('prompt.compose', async ($, e, next) => {
374 const composed = await next(e)
375 const level = await readLevel($)
376 if (level === 'off' || !(await isEnabled($))) return composed
377
378 const section = { id: 'narrator:rules', text: `${BASE_RULES}\n\n${LEVEL_RULES[level]}`, scope: 'session' } as const
379 return { sections: [...composed.sections, section] }
380 })
381
382 on('classic.Notification', async ($, e, next) => {
383 const phrase = SPOKEN_NOTIFICATIONS[e.notification_type]
384 if (phrase !== undefined) await enqueue($, serviceUrl, phrase, 'urgent')
385 return next(e)
386 }).catch(($, e, next) => next(e))
387}
388hooks/channel.ts 82 lines1// The session's channel on the relay: utterances are sealed with a key that
2// only this mod and the page URL's fragment hold, so the relay sees ciphertext.
3import nacl from './vendor/nacl.js'
4
5export type Priority = 'urgent' | 'normal'
6
7export type ChannelCredentials = {
8 id: string
9 token: string
10 key: string
11}
12
13const toBase64Url = (bytes: Uint8Array): string => {
14 let binary = ''
15 for (const byte of bytes) binary += String.fromCharCode(byte)
16 return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
17}
18
19const fromBase64Url = (text: string): Uint8Array => {
20 const base64 = text.replace(/-/g, '+').replace(/_/g, '/')
21 const binary = atob(base64 + '='.repeat((4 - (base64.length % 4)) % 4))
22 return Uint8Array.from(binary, ch => ch.charCodeAt(0))
23}
24
25const randomBase64Url = (size: number): string => toBase64Url(crypto.getRandomValues(new Uint8Array(size)))
26
27export const isCredentials = (value: unknown): value is ChannelCredentials => {
28 if (typeof value !== 'object' || value === null) return false
29 const { id, token, key } = value as Record<string, unknown>
30 return typeof id === 'string' && typeof token === 'string' && typeof key === 'string'
31}
32
33export const createCredentials = (): ChannelCredentials => ({
34 id: randomBase64Url(16),
35 token: randomBase64Url(32),
36 key: toBase64Url(nacl.randomBytes(nacl.secretbox.keyLength)),
37})
38
39export const pageUrl = (serviceUrl: string, channel: ChannelCredentials): string =>
40 `${serviceUrl}/s/${channel.id}#k=${channel.key}`
41
42export const authHeaders = (channel: ChannelCredentials): Record<string, string> => ({
43 Authorization: `Bearer ${channel.token}`,
44 'Content-Type': 'application/json',
45})
46
47export const channelUrl = (serviceUrl: string, channel: ChannelCredentials, suffix = ''): string =>
48 `${serviceUrl}/api/channels/${channel.id}${suffix}`
49
50export type OutboundKind = 'narration' | 'reply' | 'confirm' | 'heard'
51
52export type OutboundMessage = {
53 text: string
54 priority: Priority
55 kind: OutboundKind
56 ref?: string
57}
58
59export const sealMessage = (channel: ChannelCredentials, message: OutboundMessage, ts: number) => {
60 const plaintext = new TextEncoder().encode(JSON.stringify({ c: channel.id, ts, ...message }))
61 const nonce = nacl.randomBytes(nacl.secretbox.nonceLength)
62 const sealed = nacl.secretbox(plaintext, nonce, fromBase64Url(channel.key))
63 return { nonce: toBase64Url(nonce), ciphertext: toBase64Url(sealed) }
64}
65
66export const sealUtterance = (channel: ChannelCredentials, text: string, priority: Priority, ts: number) =>
67 sealMessage(channel, { text, priority, kind: 'narration' }, ts)
68
69// Opens a message the page sealed with the channel key; null when it is forged,
70// corrupted or addressed to another channel.
71export const openSealed = (channel: ChannelCredentials, nonce: string, ciphertext: string): unknown => {
72 try {
73 const opened = nacl.secretbox.open(fromBase64Url(ciphertext), fromBase64Url(nonce), fromBase64Url(channel.key))
74 if (opened === null) return null
75 const message: unknown = JSON.parse(new TextDecoder().decode(opened))
76 const isOwn = typeof message === 'object' && message !== null && (message as { c?: unknown }).c === channel.id
77 return isOwn ? message : null
78 } catch {
79 return null
80 }
81}
82hooks/sidechat.ts 161 lines1// Side chat: the developer talks to a fork of the session while the main thread
2// keeps working. Pure logic only; the hooks module does every call through $.
3
4export type ChatTurn = { role: 'developer' | 'assistant'; text: string }
5
6export type SideChatState = {
7 after: number
8 history: ChatTurn[]
9 pendingStops: Record<string, string>
10}
11
12export type Inbound =
13 | { kind: 'say'; id: string; text: string; ts: number }
14 | { kind: 'confirm'; id: string; ref: string; isConfirmed: boolean; ts: number }
15
16export type ForkDecision = {
17 reply: string
18 action: 'none' | 'note' | 'stop'
19 note: string
20}
21
22const HISTORY_LIMIT = 12
23const MAX_AGE_MS = 10 * 60 * 1000
24const MAX_TEXT = 2000
25
26export const emptyState = (): SideChatState => ({ after: 0, history: [], pendingStops: {} })
27
28export const isSideChatState = (value: unknown): value is SideChatState => {
29 if (typeof value !== 'object' || value === null) return false
30 const { after, history, pendingStops } = value as Record<string, unknown>
31 return typeof after === 'number' && Array.isArray(history) && typeof pendingStops === 'object' && pendingStops !== null
32}
33
34// Accepts only well-formed, recent messages; the relay's numbering plus `after`
35// already keeps a message from being handled twice.
36export const parseInbound = (message: unknown, now: number): Inbound | null => {
37 if (typeof message !== 'object' || message === null) return null
38 const m = message as Record<string, unknown>
39 if (typeof m.id !== 'string' || typeof m.ts !== 'number' || now - m.ts > MAX_AGE_MS) return null
40 if (m.kind === 'say' && typeof m.text === 'string' && m.text.trim() !== '') {
41 return { kind: 'say', id: m.id, text: m.text.trim().slice(0, MAX_TEXT), ts: m.ts }
42 }
43 if (m.kind === 'confirm' && typeof m.ref === 'string' && typeof m.ok === 'boolean') {
44 return { kind: 'confirm', id: m.id, ref: m.ref, isConfirmed: m.ok, ts: m.ts }
45 }
46 return null
47}
48
49export const remember = (history: readonly ChatTurn[], ...turns: ChatTurn[]): ChatTurn[] =>
50 [...history, ...turns].slice(-HISTORY_LIMIT)
51
52export const buildForkPrompt = (history: readonly ChatTurn[], text: string): string => {
53 const transcript = history.length === 0
54 ? '(this is the first message)'
55 : history.map(turn => `${turn.role === 'developer' ? 'Developer' : 'You'}: ${turn.text}`).join('\n')
56
57 return `[Side chat - this is not a new task]
58The developer is talking to you in a side chat, by voice, while your main thread keeps working on the current task. You are a fork of that main thread: you see everything it has done so far, but you cannot use tools, and nothing you say here reaches it except through "action" below.
59
60Side chat so far:
61${transcript}
62
63The developer now says:
64<message>${text}</message>
65
66Answer with one JSON object and nothing else:
67{"reply": "...", "action": "none" | "note" | "stop", "note": "..."}
68
69- "reply": what you say back aloud: one to three short conversational sentences in the developer's language, no code, paths or commands.
70- "action": "note" when the developer asks the main thread to do, avoid or change something in the ongoing work; "stop" only when they want the current work halted right now; otherwise "none".
71- "note": for "note" and "stop", the instruction for the main thread, faithful to the developer's words; otherwise "".`
72}
73
74const extractJson = (text: string): unknown => {
75 const start = text.indexOf('{')
76 const end = text.lastIndexOf('}')
77 if (start < 0 || end <= start) return null
78 try {
79 return JSON.parse(text.slice(start, end + 1))
80 } catch {
81 return null
82 }
83}
84
85// A reply that is not the requested JSON is still spoken, with no action taken.
86export const parseForkReply = (text: string): ForkDecision => {
87 const parsed = extractJson(text)
88 if (typeof parsed !== 'object' || parsed === null) return { reply: text.trim(), action: 'none', note: '' }
89 const { reply, action, note } = parsed as Record<string, unknown>
90 const safeAction = action === 'note' || action === 'stop' ? action : 'none'
91 const safeNote = typeof note === 'string' ? note.trim() : ''
92 return {
93 reply: typeof reply === 'string' ? reply.trim() : '',
94 action: safeAction !== 'none' && safeNote === '' ? 'none' : safeAction,
95 note: safeNote,
96 }
97}
98
99export const noteForMainThread = (note: string): string =>
100 `[Side chat] While you were working, the developer said in the side chat: "${note}". ` +
101 'Take it into account from your next step; do not reply in the side chat.'
102
103export type ForkUsage = {
104 forks: number
105 input_tokens: number
106 cache_creation_input_tokens: number
107 cache_read_input_tokens: number
108 output_tokens: number
109}
110
111export const emptyUsage = (): ForkUsage => ({
112 forks: 0,
113 input_tokens: 0,
114 cache_creation_input_tokens: 0,
115 cache_read_input_tokens: 0,
116 output_tokens: 0,
117})
118
119export const isForkUsage = (value: unknown): value is ForkUsage =>
120 typeof value === 'object' && value !== null && typeof (value as { forks?: unknown }).forks === 'number'
121
122const count = (value: unknown): number => (typeof value === 'number' && Number.isFinite(value) ? value : 0)
123
124export const addUsage = (total: ForkUsage, usage: Record<string, unknown> | undefined): ForkUsage => ({
125 forks: total.forks + 1,
126 input_tokens: total.input_tokens + count(usage?.input_tokens),
127 cache_creation_input_tokens: total.cache_creation_input_tokens + count(usage?.cache_creation_input_tokens),
128 cache_read_input_tokens: total.cache_read_input_tokens + count(usage?.cache_read_input_tokens),
129 output_tokens: total.output_tokens + count(usage?.output_tokens),
130})
131
132// USD per million tokens. Claude Code writes its cache with the 1-hour TTL (2x input).
133const PRICES: Record<string, { input: number; cacheWrite: number; cacheRead: number; output: number }> = {
134 'claude-opus-5-5': { input: 4, cacheWrite: 8, cacheRead: 0.2, output: 20 },
135}
136
137export const usageCost = (usage: ForkUsage, model: string): number | undefined => {
138 const price = Object.entries(PRICES).find(([id]) => model.startsWith(id))?.[1]
139 if (price === undefined) return undefined
140 return (
141 (usage.input_tokens * price.input +
142 usage.cache_creation_input_tokens * price.cacheWrite +
143 usage.cache_read_input_tokens * price.cacheRead +
144 usage.output_tokens * price.output) /
145 1_000_000
146 )
147}
148
149export const describeUsage = (usage: ForkUsage, model: string): string => {
150 const cost = usageCost(usage, model)
151 const lines = [
152 `Side chat forks: ${usage.forks}`,
153 ` input (uncached): ${usage.input_tokens.toLocaleString('en-US')}`,
154 ` cache write: ${usage.cache_creation_input_tokens.toLocaleString('en-US')}`,
155 ` cache read: ${usage.cache_read_input_tokens.toLocaleString('en-US')}`,
156 ` output: ${usage.output_tokens.toLocaleString('en-US')}`,
157 cost === undefined ? ` cost: no price table for ${model}` : ` cost: $${cost.toFixed(2)} (${model})`,
158 ]
159 return lines.join('\n')
160}
161hooks/vendor/nacl.js 2360 lines1const nacl = {};
2export default nacl;
3
4// Ported in 2014 by Dmitry Chestnykh and Devi Mandiri.
5// Public domain.
6//
7// Implementation derived from TweetNaCl version 20140427.
8// See for details: http://tweetnacl.cr.yp.to/
9
10var gf = function(init) {
11 var i, r = new Float64Array(16);
12 if (init) for (i = 0; i < init.length; i++) r[i] = init[i];
13 return r;
14};
15
16// Pluggable, initialized in high-level API below.
17var randombytes = function(/* x, n */) { throw new Error('no PRNG'); };
18
19var _0 = new Uint8Array(16);
20var _9 = new Uint8Array(32); _9[0] = 9;
21
22var gf0 = gf(),
23 gf1 = gf([1]),
24 _121665 = gf([0xdb41, 1]),
25 D = gf([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]),
26 D2 = gf([0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406]),
27 X = gf([0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169]),
28 Y = gf([0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666]),
29 I = gf([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]);
30
31function ts64(x, i, h, l) {
32 x[i] = (h >> 24) & 0xff;
33 x[i+1] = (h >> 16) & 0xff;
34 x[i+2] = (h >> 8) & 0xff;
35 x[i+3] = h & 0xff;
36 x[i+4] = (l >> 24) & 0xff;
37 x[i+5] = (l >> 16) & 0xff;
38 x[i+6] = (l >> 8) & 0xff;
39 x[i+7] = l & 0xff;
40}
41
42function vn(x, xi, y, yi, n) {
43 var i,d = 0;
44 for (i = 0; i < n; i++) d |= x[xi+i]^y[yi+i];
45 return (1 & ((d - 1) >>> 8)) - 1;
46}
47
48function crypto_verify_16(x, xi, y, yi) {
49 return vn(x,xi,y,yi,16);
50}
51
52function crypto_verify_32(x, xi, y, yi) {
53 return vn(x,xi,y,yi,32);
54}
55
56function core_salsa20(o, p, k, c) {
57 var j0 = c[ 0] & 0xff | (c[ 1] & 0xff)<<8 | (c[ 2] & 0xff)<<16 | (c[ 3] & 0xff)<<24,
58 j1 = k[ 0] & 0xff | (k[ 1] & 0xff)<<8 | (k[ 2] & 0xff)<<16 | (k[ 3] & 0xff)<<24,
59 j2 = k[ 4] & 0xff | (k[ 5] & 0xff)<<8 | (k[ 6] & 0xff)<<16 | (k[ 7] & 0xff)<<24,
60 j3 = k[ 8] & 0xff | (k[ 9] & 0xff)<<8 | (k[10] & 0xff)<<16 | (k[11] & 0xff)<<24,
61 j4 = k[12] & 0xff | (k[13] & 0xff)<<8 | (k[14] & 0xff)<<16 | (k[15] & 0xff)<<24,
62 j5 = c[ 4] & 0xff | (c[ 5] & 0xff)<<8 | (c[ 6] & 0xff)<<16 | (c[ 7] & 0xff)<<24,
63 j6 = p[ 0] & 0xff | (p[ 1] & 0xff)<<8 | (p[ 2] & 0xff)<<16 | (p[ 3] & 0xff)<<24,
64 j7 = p[ 4] & 0xff | (p[ 5] & 0xff)<<8 | (p[ 6] & 0xff)<<16 | (p[ 7] & 0xff)<<24,
65 j8 = p[ 8] & 0xff | (p[ 9] & 0xff)<<8 | (p[10] & 0xff)<<16 | (p[11] & 0xff)<<24,
66 j9 = p[12] & 0xff | (p[13] & 0xff)<<8 | (p[14] & 0xff)<<16 | (p[15] & 0xff)<<24,
67 j10 = c[ 8] & 0xff | (c[ 9] & 0xff)<<8 | (c[10] & 0xff)<<16 | (c[11] & 0xff)<<24,
68 j11 = k[16] & 0xff | (k[17] & 0xff)<<8 | (k[18] & 0xff)<<16 | (k[19] & 0xff)<<24,
69 j12 = k[20] & 0xff | (k[21] & 0xff)<<8 | (k[22] & 0xff)<<16 | (k[23] & 0xff)<<24,
70 j13 = k[24] & 0xff | (k[25] & 0xff)<<8 | (k[26] & 0xff)<<16 | (k[27] & 0xff)<<24,
71 j14 = k[28] & 0xff | (k[29] & 0xff)<<8 | (k[30] & 0xff)<<16 | (k[31] & 0xff)<<24,
72 j15 = c[12] & 0xff | (c[13] & 0xff)<<8 | (c[14] & 0xff)<<16 | (c[15] & 0xff)<<24;
73
74 var x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7,
75 x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14,
76 x15 = j15, u;
77
78 for (var i = 0; i < 20; i += 2) {
79 u = x0 + x12 | 0;
80 x4 ^= u<<7 | u>>>(32-7);
81 u = x4 + x0 | 0;
82 x8 ^= u<<9 | u>>>(32-9);
83 u = x8 + x4 | 0;
84 x12 ^= u<<13 | u>>>(32-13);
85 u = x12 + x8 | 0;
86 x0 ^= u<<18 | u>>>(32-18);
87
88 u = x5 + x1 | 0;
89 x9 ^= u<<7 | u>>>(32-7);
90 u = x9 + x5 | 0;
91 x13 ^= u<<9 | u>>>(32-9);
92 u = x13 + x9 | 0;
93 x1 ^= u<<13 | u>>>(32-13);
94 u = x1 + x13 | 0;
95 x5 ^= u<<18 | u>>>(32-18);
96
97 u = x10 + x6 | 0;
98 x14 ^= u<<7 | u>>>(32-7);
99 u = x14 + x10 | 0;
100 x2 ^= u<<9 | u>>>(32-9);
101 u = x2 + x14 | 0;
102 x6 ^= u<<13 | u>>>(32-13);
103 u = x6 + x2 | 0;
104 x10 ^= u<<18 | u>>>(32-18);
105
106 u = x15 + x11 | 0;
107 x3 ^= u<<7 | u>>>(32-7);
108 u = x3 + x15 | 0;
109 x7 ^= u<<9 | u>>>(32-9);
110 u = x7 + x3 | 0;
111 x11 ^= u<<13 | u>>>(32-13);
112 u = x11 + x7 | 0;
113 x15 ^= u<<18 | u>>>(32-18);
114
115 u = x0 + x3 | 0;
116 x1 ^= u<<7 | u>>>(32-7);
117 u = x1 + x0 | 0;
118 x2 ^= u<<9 | u>>>(32-9);
119 u = x2 + x1 | 0;
120 x3 ^= u<<13 | u>>>(32-13);
121 u = x3 + x2 | 0;
122 x0 ^= u<<18 | u>>>(32-18);
123
124 u = x5 + x4 | 0;
125 x6 ^= u<<7 | u>>>(32-7);
126 u = x6 + x5 | 0;
127 x7 ^= u<<9 | u>>>(32-9);
128 u = x7 + x6 | 0;
129 x4 ^= u<<13 | u>>>(32-13);
130 u = x4 + x7 | 0;
131 x5 ^= u<<18 | u>>>(32-18);
132
133 u = x10 + x9 | 0;
134 x11 ^= u<<7 | u>>>(32-7);
135 u = x11 + x10 | 0;
136 x8 ^= u<<9 | u>>>(32-9);
137 u = x8 + x11 | 0;
138 x9 ^= u<<13 | u>>>(32-13);
139 u = x9 + x8 | 0;
140 x10 ^= u<<18 | u>>>(32-18);
141
142 u = x15 + x14 | 0;
143 x12 ^= u<<7 | u>>>(32-7);
144 u = x12 + x15 | 0;
145 x13 ^= u<<9 | u>>>(32-9);
146 u = x13 + x12 | 0;
147 x14 ^= u<<13 | u>>>(32-13);
148 u = x14 + x13 | 0;
149 x15 ^= u<<18 | u>>>(32-18);
150 }
151 x0 = x0 + j0 | 0;
152 x1 = x1 + j1 | 0;
153 x2 = x2 + j2 | 0;
154 x3 = x3 + j3 | 0;
155 x4 = x4 + j4 | 0;
156 x5 = x5 + j5 | 0;
157 x6 = x6 + j6 | 0;
158 x7 = x7 + j7 | 0;
159 x8 = x8 + j8 | 0;
160 x9 = x9 + j9 | 0;
161 x10 = x10 + j10 | 0;
162 x11 = x11 + j11 | 0;
163 x12 = x12 + j12 | 0;
164 x13 = x13 + j13 | 0;
165 x14 = x14 + j14 | 0;
166 x15 = x15 + j15 | 0;
167
168 o[ 0] = x0 >>> 0 & 0xff;
169 o[ 1] = x0 >>> 8 & 0xff;
170 o[ 2] = x0 >>> 16 & 0xff;
171 o[ 3] = x0 >>> 24 & 0xff;
172
173 o[ 4] = x1 >>> 0 & 0xff;
174 o[ 5] = x1 >>> 8 & 0xff;
175 o[ 6] = x1 >>> 16 & 0xff;
176 o[ 7] = x1 >>> 24 & 0xff;
177
178 o[ 8] = x2 >>> 0 & 0xff;
179 o[ 9] = x2 >>> 8 & 0xff;
180 o[10] = x2 >>> 16 & 0xff;
181 o[11] = x2 >>> 24 & 0xff;
182
183 o[12] = x3 >>> 0 & 0xff;
184 o[13] = x3 >>> 8 & 0xff;
185 o[14] = x3 >>> 16 & 0xff;
186 o[15] = x3 >>> 24 & 0xff;
187
188 o[16] = x4 >>> 0 & 0xff;
189 o[17] = x4 >>> 8 & 0xff;
190 o[18] = x4 >>> 16 & 0xff;
191 o[19] = x4 >>> 24 & 0xff;
192
193 o[20] = x5 >>> 0 & 0xff;
194 o[21] = x5 >>> 8 & 0xff;
195 o[22] = x5 >>> 16 & 0xff;
196 o[23] = x5 >>> 24 & 0xff;
197
198 o[24] = x6 >>> 0 & 0xff;
199 o[25] = x6 >>> 8 & 0xff;
200 o[26] = x6 >>> 16 & 0xff;
201 o[27] = x6 >>> 24 & 0xff;
202
203 o[28] = x7 >>> 0 & 0xff;
204 o[29] = x7 >>> 8 & 0xff;
205 o[30] = x7 >>> 16 & 0xff;
206 o[31] = x7 >>> 24 & 0xff;
207
208 o[32] = x8 >>> 0 & 0xff;
209 o[33] = x8 >>> 8 & 0xff;
210 o[34] = x8 >>> 16 & 0xff;
211 o[35] = x8 >>> 24 & 0xff;
212
213 o[36] = x9 >>> 0 & 0xff;
214 o[37] = x9 >>> 8 & 0xff;
215 o[38] = x9 >>> 16 & 0xff;
216 o[39] = x9 >>> 24 & 0xff;
217
218 o[40] = x10 >>> 0 & 0xff;
219 o[41] = x10 >>> 8 & 0xff;
220 o[42] = x10 >>> 16 & 0xff;
221 o[43] = x10 >>> 24 & 0xff;
222
223 o[44] = x11 >>> 0 & 0xff;
224 o[45] = x11 >>> 8 & 0xff;
225 o[46] = x11 >>> 16 & 0xff;
226 o[47] = x11 >>> 24 & 0xff;
227
228 o[48] = x12 >>> 0 & 0xff;
229 o[49] = x12 >>> 8 & 0xff;
230 o[50] = x12 >>> 16 & 0xff;
231 o[51] = x12 >>> 24 & 0xff;
232
233 o[52] = x13 >>> 0 & 0xff;
234 o[53] = x13 >>> 8 & 0xff;
235 o[54] = x13 >>> 16 & 0xff;
236 o[55] = x13 >>> 24 & 0xff;
237
238 o[56] = x14 >>> 0 & 0xff;
239 o[57] = x14 >>> 8 & 0xff;
240 o[58] = x14 >>> 16 & 0xff;
241 o[59] = x14 >>> 24 & 0xff;
242
243 o[60] = x15 >>> 0 & 0xff;
244 o[61] = x15 >>> 8 & 0xff;
245 o[62] = x15 >>> 16 & 0xff;
246 o[63] = x15 >>> 24 & 0xff;
247}
248
249function core_hsalsa20(o,p,k,c) {
250 var j0 = c[ 0] & 0xff | (c[ 1] & 0xff)<<8 | (c[ 2] & 0xff)<<16 | (c[ 3] & 0xff)<<24,
251 j1 = k[ 0] & 0xff | (k[ 1] & 0xff)<<8 | (k[ 2] & 0xff)<<16 | (k[ 3] & 0xff)<<24,
252 j2 = k[ 4] & 0xff | (k[ 5] & 0xff)<<8 | (k[ 6] & 0xff)<<16 | (k[ 7] & 0xff)<<24,
253 j3 = k[ 8] & 0xff | (k[ 9] & 0xff)<<8 | (k[10] & 0xff)<<16 | (k[11] & 0xff)<<24,
254 j4 = k[12] & 0xff | (k[13] & 0xff)<<8 | (k[14] & 0xff)<<16 | (k[15] & 0xff)<<24,
255 j5 = c[ 4] & 0xff | (c[ 5] & 0xff)<<8 | (c[ 6] & 0xff)<<16 | (c[ 7] & 0xff)<<24,
256 j6 = p[ 0] & 0xff | (p[ 1] & 0xff)<<8 | (p[ 2] & 0xff)<<16 | (p[ 3] & 0xff)<<24,
257 j7 = p[ 4] & 0xff | (p[ 5] & 0xff)<<8 | (p[ 6] & 0xff)<<16 | (p[ 7] & 0xff)<<24,
258 j8 = p[ 8] & 0xff | (p[ 9] & 0xff)<<8 | (p[10] & 0xff)<<16 | (p[11] & 0xff)<<24,
259 j9 = p[12] & 0xff | (p[13] & 0xff)<<8 | (p[14] & 0xff)<<16 | (p[15] & 0xff)<<24,
260 j10 = c[ 8] & 0xff | (c[ 9] & 0xff)<<8 | (c[10] & 0xff)<<16 | (c[11] & 0xff)<<24,
261 j11 = k[16] & 0xff | (k[17] & 0xff)<<8 | (k[18] & 0xff)<<16 | (k[19] & 0xff)<<24,
262 j12 = k[20] & 0xff | (k[21] & 0xff)<<8 | (k[22] & 0xff)<<16 | (k[23] & 0xff)<<24,
263 j13 = k[24] & 0xff | (k[25] & 0xff)<<8 | (k[26] & 0xff)<<16 | (k[27] & 0xff)<<24,
264 j14 = k[28] & 0xff | (k[29] & 0xff)<<8 | (k[30] & 0xff)<<16 | (k[31] & 0xff)<<24,
265 j15 = c[12] & 0xff | (c[13] & 0xff)<<8 | (c[14] & 0xff)<<16 | (c[15] & 0xff)<<24;
266
267 var x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7,
268 x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14,
269 x15 = j15, u;
270
271 for (var i = 0; i < 20; i += 2) {
272 u = x0 + x12 | 0;
273 x4 ^= u<<7 | u>>>(32-7);
274 u = x4 + x0 | 0;
275 x8 ^= u<<9 | u>>>(32-9);
276 u = x8 + x4 | 0;
277 x12 ^= u<<13 | u>>>(32-13);
278 u = x12 + x8 | 0;
279 x0 ^= u<<18 | u>>>(32-18);
280
281 u = x5 + x1 | 0;
282 x9 ^= u<<7 | u>>>(32-7);
283 u = x9 + x5 | 0;
284 x13 ^= u<<9 | u>>>(32-9);
285 u = x13 + x9 | 0;
286 x1 ^= u<<13 | u>>>(32-13);
287 u = x1 + x13 | 0;
288 x5 ^= u<<18 | u>>>(32-18);
289
290 u = x10 + x6 | 0;
291 x14 ^= u<<7 | u>>>(32-7);
292 u = x14 + x10 | 0;
293 x2 ^= u<<9 | u>>>(32-9);
294 u = x2 + x14 | 0;
295 x6 ^= u<<13 | u>>>(32-13);
296 u = x6 + x2 | 0;
297 x10 ^= u<<18 | u>>>(32-18);
298
299 u = x15 + x11 | 0;
300 x3 ^= u<<7 | u>>>(32-7);
301 u = x3 + x15 | 0;
302 x7 ^= u<<9 | u>>>(32-9);
303 u = x7 + x3 | 0;
304 x11 ^= u<<13 | u>>>(32-13);
305 u = x11 + x7 | 0;
306 x15 ^= u<<18 | u>>>(32-18);
307
308 u = x0 + x3 | 0;
309 x1 ^= u<<7 | u>>>(32-7);
310 u = x1 + x0 | 0;
311 x2 ^= u<<9 | u>>>(32-9);
312 u = x2 + x1 | 0;
313 x3 ^= u<<13 | u>>>(32-13);
314 u = x3 + x2 | 0;
315 x0 ^= u<<18 | u>>>(32-18);
316
317 u = x5 + x4 | 0;
318 x6 ^= u<<7 | u>>>(32-7);
319 u = x6 + x5 | 0;
320 x7 ^= u<<9 | u>>>(32-9);
321 u = x7 + x6 | 0;
322 x4 ^= u<<13 | u>>>(32-13);
323 u = x4 + x7 | 0;
324 x5 ^= u<<18 | u>>>(32-18);
325
326 u = x10 + x9 | 0;
327 x11 ^= u<<7 | u>>>(32-7);
328 u = x11 + x10 | 0;
329 x8 ^= u<<9 | u>>>(32-9);
330 u = x8 + x11 | 0;
331 x9 ^= u<<13 | u>>>(32-13);
332 u = x9 + x8 | 0;
333 x10 ^= u<<18 | u>>>(32-18);
334
335 u = x15 + x14 | 0;
336 x12 ^= u<<7 | u>>>(32-7);
337 u = x12 + x15 | 0;
338 x13 ^= u<<9 | u>>>(32-9);
339 u = x13 + x12 | 0;
340 x14 ^= u<<13 | u>>>(32-13);
341 u = x14 + x13 | 0;
342 x15 ^= u<<18 | u>>>(32-18);
343 }
344
345 o[ 0] = x0 >>> 0 & 0xff;
346 o[ 1] = x0 >>> 8 & 0xff;
347 o[ 2] = x0 >>> 16 & 0xff;
348 o[ 3] = x0 >>> 24 & 0xff;
349
350 o[ 4] = x5 >>> 0 & 0xff;
351 o[ 5] = x5 >>> 8 & 0xff;
352 o[ 6] = x5 >>> 16 & 0xff;
353 o[ 7] = x5 >>> 24 & 0xff;
354
355 o[ 8] = x10 >>> 0 & 0xff;
356 o[ 9] = x10 >>> 8 & 0xff;
357 o[10] = x10 >>> 16 & 0xff;
358 o[11] = x10 >>> 24 & 0xff;
359
360 o[12] = x15 >>> 0 & 0xff;
361 o[13] = x15 >>> 8 & 0xff;
362 o[14] = x15 >>> 16 & 0xff;
363 o[15] = x15 >>> 24 & 0xff;
364
365 o[16] = x6 >>> 0 & 0xff;
366 o[17] = x6 >>> 8 & 0xff;
367 o[18] = x6 >>> 16 & 0xff;
368 o[19] = x6 >>> 24 & 0xff;
369
370 o[20] = x7 >>> 0 & 0xff;
371 o[21] = x7 >>> 8 & 0xff;
372 o[22] = x7 >>> 16 & 0xff;
373 o[23] = x7 >>> 24 & 0xff;
374
375 o[24] = x8 >>> 0 & 0xff;
376 o[25] = x8 >>> 8 & 0xff;
377 o[26] = x8 >>> 16 & 0xff;
378 o[27] = x8 >>> 24 & 0xff;
379
380 o[28] = x9 >>> 0 & 0xff;
381 o[29] = x9 >>> 8 & 0xff;
382 o[30] = x9 >>> 16 & 0xff;
383 o[31] = x9 >>> 24 & 0xff;
384}
385
386function crypto_core_salsa20(out,inp,k,c) {
387 core_salsa20(out,inp,k,c);
388}
389
390function crypto_core_hsalsa20(out,inp,k,c) {
391 core_hsalsa20(out,inp,k,c);
392}
393
394var sigma = new Uint8Array([101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98, 121, 116, 101, 32, 107]);
395 // "expand 32-byte k"
396
397function crypto_stream_salsa20_xor(c,cpos,m,mpos,b,n,k) {
398 var z = new Uint8Array(16), x = new Uint8Array(64);
399 var u, i;
400 for (i = 0; i < 16; i++) z[i] = 0;
401 for (i = 0; i < 8; i++) z[i] = n[i];
402 while (b >= 64) {
403 crypto_core_salsa20(x,z,k,sigma);
404 for (i = 0; i < 64; i++) c[cpos+i] = m[mpos+i] ^ x[i];
405 u = 1;
406 for (i = 8; i < 16; i++) {
407 u = u + (z[i] & 0xff) | 0;
408 z[i] = u & 0xff;
409 u >>>= 8;
410 }
411 b -= 64;
412 cpos += 64;
413 mpos += 64;
414 }
415 if (b > 0) {
416 crypto_core_salsa20(x,z,k,sigma);
417 for (i = 0; i < b; i++) c[cpos+i] = m[mpos+i] ^ x[i];
418 }
419 return 0;
420}
421
422function crypto_stream_salsa20(c,cpos,b,n,k) {
423 var z = new Uint8Array(16), x = new Uint8Array(64);
424 var u, i;
425 for (i = 0; i < 16; i++) z[i] = 0;
426 for (i = 0; i < 8; i++) z[i] = n[i];
427 while (b >= 64) {
428 crypto_core_salsa20(x,z,k,sigma);
429 for (i = 0; i < 64; i++) c[cpos+i] = x[i];
430 u = 1;
431 for (i = 8; i < 16; i++) {
432 u = u + (z[i] & 0xff) | 0;
433 z[i] = u & 0xff;
434 u >>>= 8;
435 }
436 b -= 64;
437 cpos += 64;
438 }
439 if (b > 0) {
440 crypto_core_salsa20(x,z,k,sigma);
441 for (i = 0; i < b; i++) c[cpos+i] = x[i];
442 }
443 return 0;
444}
445
446function crypto_stream(c,cpos,d,n,k) {
447 var s = new Uint8Array(32);
448 crypto_core_hsalsa20(s,n,k,sigma);
449 var sn = new Uint8Array(8);
450 for (var i = 0; i < 8; i++) sn[i] = n[i+16];
451 return crypto_stream_salsa20(c,cpos,d,sn,s);
452}
453
454function crypto_stream_xor(c,cpos,m,mpos,d,n,k) {
455 var s = new Uint8Array(32);
456 crypto_core_hsalsa20(s,n,k,sigma);
457 var sn = new Uint8Array(8);
458 for (var i = 0; i < 8; i++) sn[i] = n[i+16];
459 return crypto_stream_salsa20_xor(c,cpos,m,mpos,d,sn,s);
460}
461
462/*
463* Port of Andrew Moon's Poly1305-donna-16. Public domain.
464* https://github.com/floodyberry/poly1305-donna
465*/
466
467var poly1305 = function(key) {
468 this.buffer = new Uint8Array(16);
469 this.r = new Uint16Array(10);
470 this.h = new Uint16Array(10);
471 this.pad = new Uint16Array(8);
472 this.leftover = 0;
473 this.fin = 0;
474
475 var t0, t1, t2, t3, t4, t5, t6, t7;
476
477 t0 = key[ 0] & 0xff | (key[ 1] & 0xff) << 8; this.r[0] = ( t0 ) & 0x1fff;
478 t1 = key[ 2] & 0xff | (key[ 3] & 0xff) << 8; this.r[1] = ((t0 >>> 13) | (t1 << 3)) & 0x1fff;
479 t2 = key[ 4] & 0xff | (key[ 5] & 0xff) << 8; this.r[2] = ((t1 >>> 10) | (t2 << 6)) & 0x1f03;
480 t3 = key[ 6] & 0xff | (key[ 7] & 0xff) << 8; this.r[3] = ((t2 >>> 7) | (t3 << 9)) & 0x1fff;
481 t4 = key[ 8] & 0xff | (key[ 9] & 0xff) << 8; this.r[4] = ((t3 >>> 4) | (t4 << 12)) & 0x00ff;
482 this.r[5] = ((t4 >>> 1)) & 0x1ffe;
483 t5 = key[10] & 0xff | (key[11] & 0xff) << 8; this.r[6] = ((t4 >>> 14) | (t5 << 2)) & 0x1fff;
484 t6 = key[12] & 0xff | (key[13] & 0xff) << 8; this.r[7] = ((t5 >>> 11) | (t6 << 5)) & 0x1f81;
485 t7 = key[14] & 0xff | (key[15] & 0xff) << 8; this.r[8] = ((t6 >>> 8) | (t7 << 8)) & 0x1fff;
486 this.r[9] = ((t7 >>> 5)) & 0x007f;
487
488 this.pad[0] = key[16] & 0xff | (key[17] & 0xff) << 8;
489 this.pad[1] = key[18] & 0xff | (key[19] & 0xff) << 8;
490 this.pad[2] = key[20] & 0xff | (key[21] & 0xff) << 8;
491 this.pad[3] = key[22] & 0xff | (key[23] & 0xff) << 8;
492 this.pad[4] = key[24] & 0xff | (key[25] & 0xff) << 8;
493 this.pad[5] = key[26] & 0xff | (key[27] & 0xff) << 8;
494 this.pad[6] = key[28] & 0xff | (key[29] & 0xff) << 8;
495 this.pad[7] = key[30] & 0xff | (key[31] & 0xff) << 8;
496};
497
498poly1305.prototype.blocks = function(m, mpos, bytes) {
499 var hibit = this.fin ? 0 : (1 << 11);
500 var t0, t1, t2, t3, t4, t5, t6, t7, c;
501 var d0, d1, d2, d3, d4, d5, d6, d7, d8, d9;
502
503 var h0 = this.h[0],
504 h1 = this.h[1],
505 h2 = this.h[2],
506 h3 = this.h[3],
507 h4 = this.h[4],
508 h5 = this.h[5],
509 h6 = this.h[6],
510 h7 = this.h[7],
511 h8 = this.h[8],
512 h9 = this.h[9];
513
514 var r0 = this.r[0],
515 r1 = this.r[1],
516 r2 = this.r[2],
517 r3 = this.r[3],
518 r4 = this.r[4],
519 r5 = this.r[5],
520 r6 = this.r[6],
521 r7 = this.r[7],
522 r8 = this.r[8],
523 r9 = this.r[9];
524
525 while (bytes >= 16) {
526 t0 = m[mpos+ 0] & 0xff | (m[mpos+ 1] & 0xff) << 8; h0 += ( t0 ) & 0x1fff;
527 t1 = m[mpos+ 2] & 0xff | (m[mpos+ 3] & 0xff) << 8; h1 += ((t0 >>> 13) | (t1 << 3)) & 0x1fff;
528 t2 = m[mpos+ 4] & 0xff | (m[mpos+ 5] & 0xff) << 8; h2 += ((t1 >>> 10) | (t2 << 6)) & 0x1fff;
529 t3 = m[mpos+ 6] & 0xff | (m[mpos+ 7] & 0xff) << 8; h3 += ((t2 >>> 7) | (t3 << 9)) & 0x1fff;
530 t4 = m[mpos+ 8] & 0xff | (m[mpos+ 9] & 0xff) << 8; h4 += ((t3 >>> 4) | (t4 << 12)) & 0x1fff;
531 h5 += ((t4 >>> 1)) & 0x1fff;
532 t5 = m[mpos+10] & 0xff | (m[mpos+11] & 0xff) << 8; h6 += ((t4 >>> 14) | (t5 << 2)) & 0x1fff;
533 t6 = m[mpos+12] & 0xff | (m[mpos+13] & 0xff) << 8; h7 += ((t5 >>> 11) | (t6 << 5)) & 0x1fff;
534 t7 = m[mpos+14] & 0xff | (m[mpos+15] & 0xff) << 8; h8 += ((t6 >>> 8) | (t7 << 8)) & 0x1fff;
535 h9 += ((t7 >>> 5)) | hibit;
536
537 c = 0;
538
539 d0 = c;
540 d0 += h0 * r0;
541 d0 += h1 * (5 * r9);
542 d0 += h2 * (5 * r8);
543 d0 += h3 * (5 * r7);
544 d0 += h4 * (5 * r6);
545 c = (d0 >>> 13); d0 &= 0x1fff;
546 d0 += h5 * (5 * r5);
547 d0 += h6 * (5 * r4);
548 d0 += h7 * (5 * r3);
549 d0 += h8 * (5 * r2);
550 d0 += h9 * (5 * r1);
551 c += (d0 >>> 13); d0 &= 0x1fff;
552
553 d1 = c;
554 d1 += h0 * r1;
555 d1 += h1 * r0;
556 d1 += h2 * (5 * r9);
557 d1 += h3 * (5 * r8);
558 d1 += h4 * (5 * r7);
559 c = (d1 >>> 13); d1 &= 0x1fff;
560 d1 += h5 * (5 * r6);
561 d1 += h6 * (5 * r5);
562 d1 += h7 * (5 * r4);
563 d1 += h8 * (5 * r3);
564 d1 += h9 * (5 * r2);
565 c += (d1 >>> 13); d1 &= 0x1fff;
566
567 d2 = c;
568 d2 += h0 * r2;
569 d2 += h1 * r1;
570 d2 += h2 * r0;
571 d2 += h3 * (5 * r9);
572 d2 += h4 * (5 * r8);
573 c = (d2 >>> 13); d2 &= 0x1fff;
574 d2 += h5 * (5 * r7);
575 d2 += h6 * (5 * r6);
576 d2 += h7 * (5 * r5);
577 d2 += h8 * (5 * r4);
578 d2 += h9 * (5 * r3);
579 c += (d2 >>> 13); d2 &= 0x1fff;
580
581 d3 = c;
582 d3 += h0 * r3;
583 d3 += h1 * r2;
584 d3 += h2 * r1;
585 d3 += h3 * r0;
586 d3 += h4 * (5 * r9);
587 c = (d3 >>> 13); d3 &= 0x1fff;
588 d3 += h5 * (5 * r8);
589 d3 += h6 * (5 * r7);
590 d3 += h7 * (5 * r6);
591 d3 += h8 * (5 * r5);
592 d3 += h9 * (5 * r4);
593 c += (d3 >>> 13); d3 &= 0x1fff;
594
595 d4 = c;
596 d4 += h0 * r4;
597 d4 += h1 * r3;
598 d4 += h2 * r2;
599 d4 += h3 * r1;
600 d4 += h4 * r0;
601 c = (d4 >>> 13); d4 &= 0x1fff;
602 d4 += h5 * (5 * r9);
603 d4 += h6 * (5 * r8);
604 d4 += h7 * (5 * r7);
605 d4 += h8 * (5 * r6);
606 d4 += h9 * (5 * r5);
607 c += (d4 >>> 13); d4 &= 0x1fff;
608
609 d5 = c;
610 d5 += h0 * r5;
611 d5 += h1 * r4;
612 d5 += h2 * r3;
613 d5 += h3 * r2;
614 d5 += h4 * r1;
615 c = (d5 >>> 13); d5 &= 0x1fff;
616 d5 += h5 * r0;
617 d5 += h6 * (5 * r9);
618 d5 += h7 * (5 * r8);
619 d5 += h8 * (5 * r7);
620 d5 += h9 * (5 * r6);
621 c += (d5 >>> 13); d5 &= 0x1fff;
622
623 d6 = c;
624 d6 += h0 * r6;
625 d6 += h1 * r5;
626 d6 += h2 * r4;
627 d6 += h3 * r3;
628 d6 += h4 * r2;
629 c = (d6 >>> 13); d6 &= 0x1fff;
630 d6 += h5 * r1;
631 d6 += h6 * r0;
632 d6 += h7 * (5 * r9);
633 d6 += h8 * (5 * r8);
634 d6 += h9 * (5 * r7);
635 c += (d6 >>> 13); d6 &= 0x1fff;
636
637 d7 = c;
638 d7 += h0 * r7;
639 d7 += h1 * r6;
640 d7 += h2 * r5;
641 d7 += h3 * r4;
642 d7 += h4 * r3;
643 c = (d7 >>> 13); d7 &= 0x1fff;
644 d7 += h5 * r2;
645 d7 += h6 * r1;
646 d7 += h7 * r0;
647 d7 += h8 * (5 * r9);
648 d7 += h9 * (5 * r8);
649 c += (d7 >>> 13); d7 &= 0x1fff;
650
651 d8 = c;
652 d8 += h0 * r8;
653 d8 += h1 * r7;
654 d8 += h2 * r6;
655 d8 += h3 * r5;
656 d8 += h4 * r4;
657 c = (d8 >>> 13); d8 &= 0x1fff;
658 d8 += h5 * r3;
659 d8 += h6 * r2;
660 d8 += h7 * r1;
661 d8 += h8 * r0;
662 d8 += h9 * (5 * r9);
663 c += (d8 >>> 13); d8 &= 0x1fff;
664
665 d9 = c;
666 d9 += h0 * r9;
667 d9 += h1 * r8;
668 d9 += h2 * r7;
669 d9 += h3 * r6;
670 d9 += h4 * r5;
671 c = (d9 >>> 13); d9 &= 0x1fff;
672 d9 += h5 * r4;
673 d9 += h6 * r3;
674 d9 += h7 * r2;
675 d9 += h8 * r1;
676 d9 += h9 * r0;
677 c += (d9 >>> 13); d9 &= 0x1fff;
678
679 c = (((c << 2) + c)) | 0;
680 c = (c + d0) | 0;
681 d0 = c & 0x1fff;
682 c = (c >>> 13);
683 d1 += c;
684
685 h0 = d0;
686 h1 = d1;
687 h2 = d2;
688 h3 = d3;
689 h4 = d4;
690 h5 = d5;
691 h6 = d6;
692 h7 = d7;
693 h8 = d8;
694 h9 = d9;
695
696 mpos += 16;
697 bytes -= 16;
698 }
699 this.h[0] = h0;
700 this.h[1] = h1;
701 this.h[2] = h2;
702 this.h[3] = h3;
703 this.h[4] = h4;
704 this.h[5] = h5;
705 this.h[6] = h6;
706 this.h[7] = h7;
707 this.h[8] = h8;
708 this.h[9] = h9;
709};
710
711poly1305.prototype.finish = function(mac, macpos) {
712 var g = new Uint16Array(10);
713 var c, mask, f, i;
714
715 if (this.leftover) {
716 i = this.leftover;
717 this.buffer[i++] = 1;
718 for (; i < 16; i++) this.buffer[i] = 0;
719 this.fin = 1;
720 this.blocks(this.buffer, 0, 16);
721 }
722
723 c = this.h[1] >>> 13;
724 this.h[1] &= 0x1fff;
725 for (i = 2; i < 10; i++) {
726 this.h[i] += c;
727 c = this.h[i] >>> 13;
728 this.h[i] &= 0x1fff;
729 }
730 this.h[0] += (c * 5);
731 c = this.h[0] >>> 13;
732 this.h[0] &= 0x1fff;
733 this.h[1] += c;
734 c = this.h[1] >>> 13;
735 this.h[1] &= 0x1fff;
736 this.h[2] += c;
737
738 g[0] = this.h[0] + 5;
739 c = g[0] >>> 13;
740 g[0] &= 0x1fff;
741 for (i = 1; i < 10; i++) {
742 g[i] = this.h[i] + c;
743 c = g[i] >>> 13;
744 g[i] &= 0x1fff;
745 }
746 g[9] -= (1 << 13);
747
748 mask = (c ^ 1) - 1;
749 for (i = 0; i < 10; i++) g[i] &= mask;
750 mask = ~mask;
751 for (i = 0; i < 10; i++) this.h[i] = (this.h[i] & mask) | g[i];
752
753 this.h[0] = ((this.h[0] ) | (this.h[1] << 13) ) & 0xffff;
754 this.h[1] = ((this.h[1] >>> 3) | (this.h[2] << 10) ) & 0xffff;
755 this.h[2] = ((this.h[2] >>> 6) | (this.h[3] << 7) ) & 0xffff;
756 this.h[3] = ((this.h[3] >>> 9) | (this.h[4] << 4) ) & 0xffff;
757 this.h[4] = ((this.h[4] >>> 12) | (this.h[5] << 1) | (this.h[6] << 14)) & 0xffff;
758 this.h[5] = ((this.h[6] >>> 2) | (this.h[7] << 11) ) & 0xffff;
759 this.h[6] = ((this.h[7] >>> 5) | (this.h[8] << 8) ) & 0xffff;
760 this.h[7] = ((this.h[8] >>> 8) | (this.h[9] << 5) ) & 0xffff;
761
762 f = this.h[0] + this.pad[0];
763 this.h[0] = f & 0xffff;
764 for (i = 1; i < 8; i++) {
765 f = (((this.h[i] + this.pad[i]) | 0) + (f >>> 16)) | 0;
766 this.h[i] = f & 0xffff;
767 }
768
769 mac[macpos+ 0] = (this.h[0] >>> 0) & 0xff;
770 mac[macpos+ 1] = (this.h[0] >>> 8) & 0xff;
771 mac[macpos+ 2] = (this.h[1] >>> 0) & 0xff;
772 mac[macpos+ 3] = (this.h[1] >>> 8) & 0xff;
773 mac[macpos+ 4] = (this.h[2] >>> 0) & 0xff;
774 mac[macpos+ 5] = (this.h[2] >>> 8) & 0xff;
775 mac[macpos+ 6] = (this.h[3] >>> 0) & 0xff;
776 mac[macpos+ 7] = (this.h[3] >>> 8) & 0xff;
777 mac[macpos+ 8] = (this.h[4] >>> 0) & 0xff;
778 mac[macpos+ 9] = (this.h[4] >>> 8) & 0xff;
779 mac[macpos+10] = (this.h[5] >>> 0) & 0xff;
780 mac[macpos+11] = (this.h[5] >>> 8) & 0xff;
781 mac[macpos+12] = (this.h[6] >>> 0) & 0xff;
782 mac[macpos+13] = (this.h[6] >>> 8) & 0xff;
783 mac[macpos+14] = (this.h[7] >>> 0) & 0xff;
784 mac[macpos+15] = (this.h[7] >>> 8) & 0xff;
785};
786
787poly1305.prototype.update = function(m, mpos, bytes) {
788 var i, want;
789
790 if (this.leftover) {
791 want = (16 - this.leftover);
792 if (want > bytes)
793 want = bytes;
794 for (i = 0; i < want; i++)
795 this.buffer[this.leftover + i] = m[mpos+i];
796 bytes -= want;
797 mpos += want;
798 this.leftover += want;
799 if (this.leftover < 16)
800 return;
801 this.blocks(this.buffer, 0, 16);
802 this.leftover = 0;
803 }
804
805 if (bytes >= 16) {
806 want = bytes - (bytes % 16);
807 this.blocks(m, mpos, want);
808 mpos += want;
809 bytes -= want;
810 }
811
812 if (bytes) {
813 for (i = 0; i < bytes; i++)
814 this.buffer[this.leftover + i] = m[mpos+i];
815 this.leftover += bytes;
816 }
817};
818
819function crypto_onetimeauth(out, outpos, m, mpos, n, k) {
820 var s = new poly1305(k);
821 s.update(m, mpos, n);
822 s.finish(out, outpos);
823 return 0;
824}
825
826function crypto_onetimeauth_verify(h, hpos, m, mpos, n, k) {
827 var x = new Uint8Array(16);
828 crypto_onetimeauth(x,0,m,mpos,n,k);
829 return crypto_verify_16(h,hpos,x,0);
830}
831
832function crypto_secretbox(c,m,d,n,k) {
833 var i;
834 if (d < 32) return -1;
835 crypto_stream_xor(c,0,m,0,d,n,k);
836 crypto_onetimeauth(c, 16, c, 32, d - 32, c);
837 for (i = 0; i < 16; i++) c[i] = 0;
838 return 0;
839}
840
841function crypto_secretbox_open(m,c,d,n,k) {
842 var i;
843 var x = new Uint8Array(32);
844 if (d < 32) return -1;
845 crypto_stream(x,0,32,n,k);
846 if (crypto_onetimeauth_verify(c, 16,c, 32,d - 32,x) !== 0) return -1;
847 crypto_stream_xor(m,0,c,0,d,n,k);
848 for (i = 0; i < 32; i++) m[i] = 0;
849 return 0;
850}
851
852function set25519(r, a) {
853 var i;
854 for (i = 0; i < 16; i++) r[i] = a[i]|0;
855}
856
857function car25519(o) {
858 var i, v, c = 1;
859 for (i = 0; i < 16; i++) {
860 v = o[i] + c + 65535;
861 c = Math.floor(v / 65536);
862 o[i] = v - c * 65536;
863 }
864 o[0] += c-1 + 37 * (c-1);
865}
866
867function sel25519(p, q, b) {
868 var t, c = ~(b-1);
869 for (var i = 0; i < 16; i++) {
870 t = c & (p[i] ^ q[i]);
871 p[i] ^= t;
872 q[i] ^= t;
873 }
874}
875
876function pack25519(o, n) {
877 var i, j, b;
878 var m = gf(), t = gf();
879 for (i = 0; i < 16; i++) t[i] = n[i];
880 car25519(t);
881 car25519(t);
882 car25519(t);
883 for (j = 0; j < 2; j++) {
884 m[0] = t[0] - 0xffed;
885 for (i = 1; i < 15; i++) {
886 m[i] = t[i] - 0xffff - ((m[i-1]>>16) & 1);
887 m[i-1] &= 0xffff;
888 }
889 m[15] = t[15] - 0x7fff - ((m[14]>>16) & 1);
890 b = (m[15]>>16) & 1;
891 m[14] &= 0xffff;
892 sel25519(t, m, 1-b);
893 }
894 for (i = 0; i < 16; i++) {
895 o[2*i] = t[i] & 0xff;
896 o[2*i+1] = t[i]>>8;
897 }
898}
899
900function neq25519(a, b) {
901 var c = new Uint8Array(32), d = new Uint8Array(32);
902 pack25519(c, a);
903 pack25519(d, b);
904 return crypto_verify_32(c, 0, d, 0);
905}
906
907function par25519(a) {
908 var d = new Uint8Array(32);
909 pack25519(d, a);
910 return d[0] & 1;
911}
912
913function unpack25519(o, n) {
914 var i;
915 for (i = 0; i < 16; i++) o[i] = n[2*i] + (n[2*i+1] << 8);
916 o[15] &= 0x7fff;
917}
918
919function A(o, a, b) {
920 for (var i = 0; i < 16; i++) o[i] = a[i] + b[i];
921}
922
923function Z(o, a, b) {
924 for (var i = 0; i < 16; i++) o[i] = a[i] - b[i];
925}
926
927function M(o, a, b) {
928 var v, c,
929 t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, t7 = 0,
930 t8 = 0, t9 = 0, t10 = 0, t11 = 0, t12 = 0, t13 = 0, t14 = 0, t15 = 0,
931 t16 = 0, t17 = 0, t18 = 0, t19 = 0, t20 = 0, t21 = 0, t22 = 0, t23 = 0,
932 t24 = 0, t25 = 0, t26 = 0, t27 = 0, t28 = 0, t29 = 0, t30 = 0,
933 b0 = b[0],
934 b1 = b[1],
935 b2 = b[2],
936 b3 = b[3],
937 b4 = b[4],
938 b5 = b[5],
939 b6 = b[6],
940 b7 = b[7],
941 b8 = b[8],
942 b9 = b[9],
943 b10 = b[10],
944 b11 = b[11],
945 b12 = b[12],
946 b13 = b[13],
947 b14 = b[14],
948 b15 = b[15];
949
950 v = a[0];
951 t0 += v * b0;
952 t1 += v * b1;
953 t2 += v * b2;
954 t3 += v * b3;
955 t4 += v * b4;
956 t5 += v * b5;
957 t6 += v * b6;
958 t7 += v * b7;
959 t8 += v * b8;
960 t9 += v * b9;
961 t10 += v * b10;
962 t11 += v * b11;
963 t12 += v * b12;
964 t13 += v * b13;
965 t14 += v * b14;
966 t15 += v * b15;
967 v = a[1];
968 t1 += v * b0;
969 t2 += v * b1;
970 t3 += v * b2;
971 t4 += v * b3;
972 t5 += v * b4;
973 t6 += v * b5;
974 t7 += v * b6;
975 t8 += v * b7;
976 t9 += v * b8;
977 t10 += v * b9;
978 t11 += v * b10;
979 t12 += v * b11;
980 t13 += v * b12;
981 t14 += v * b13;
982 t15 += v * b14;
983 t16 += v * b15;
984 v = a[2];
985 t2 += v * b0;
986 t3 += v * b1;
987 t4 += v * b2;
988 t5 += v * b3;
989 t6 += v * b4;
990 t7 += v * b5;
991 t8 += v * b6;
992 t9 += v * b7;
993 t10 += v * b8;
994 t11 += v * b9;
995 t12 += v * b10;
996 t13 += v * b11;
997 t14 += v * b12;
998 t15 += v * b13;
999 t16 += v * b14;
1000 t17 += v * b15;
1001 v = a[3];
1002 t3 += v * b0;
1003 t4 += v * b1;
1004 t5 += v * b2;
1005 t6 += v * b3;
1006 t7 += v * b4;
1007 t8 += v * b5;
1008 t9 += v * b6;
1009 t10 += v * b7;
1010 t11 += v * b8;
1011 t12 += v * b9;
1012 t13 += v * b10;
1013 t14 += v * b11;
1014 t15 += v * b12;
1015 t16 += v * b13;
1016 t17 += v * b14;
1017 t18 += v * b15;
1018 v = a[4];
1019 t4 += v * b0;
1020 t5 += v * b1;
1021 t6 += v * b2;
1022 t7 += v * b3;
1023 t8 += v * b4;
1024 t9 += v * b5;
1025 t10 += v * b6;
1026 t11 += v * b7;
1027 t12 += v * b8;
1028 t13 += v * b9;
1029 t14 += v * b10;
1030 t15 += v * b11;
1031 t16 += v * b12;
1032 t17 += v * b13;
1033 t18 += v * b14;
1034 t19 += v * b15;
1035 v = a[5];
1036 t5 += v * b0;
1037 t6 += v * b1;
1038 t7 += v * b2;
1039 t8 += v * b3;
1040 t9 += v * b4;
1041 t10 += v * b5;
1042 t11 += v * b6;
1043 t12 += v * b7;
1044 t13 += v * b8;
1045 t14 += v * b9;
1046 t15 += v * b10;
1047 t16 += v * b11;
1048 t17 += v * b12;
1049 t18 += v * b13;
1050 t19 += v * b14;
1051 t20 += v * b15;
1052 v = a[6];
1053 t6 += v * b0;
1054 t7 += v * b1;
1055 t8 += v * b2;
1056 t9 += v * b3;
1057 t10 += v * b4;
1058 t11 += v * b5;
1059 t12 += v * b6;
1060 t13 += v * b7;
1061 t14 += v * b8;
1062 t15 += v * b9;
1063 t16 += v * b10;
1064 t17 += v * b11;
1065 t18 += v * b12;
1066 t19 += v * b13;
1067 t20 += v * b14;
1068 t21 += v * b15;
1069 v = a[7];
1070 t7 += v * b0;
1071 t8 += v * b1;
1072 t9 += v * b2;
1073 t10 += v * b3;
1074 t11 += v * b4;
1075 t12 += v * b5;
1076 t13 += v * b6;
1077 t14 += v * b7;
1078 t15 += v * b8;
1079 t16 += v * b9;
1080 t17 += v * b10;
1081 t18 += v * b11;
1082 t19 += v * b12;
1083 t20 += v * b13;
1084 t21 += v * b14;
1085 t22 += v * b15;
1086 v = a[8];
1087 t8 += v * b0;
1088 t9 += v * b1;
1089 t10 += v * b2;
1090 t11 += v * b3;
1091 t12 += v * b4;
1092 t13 += v * b5;
1093 t14 += v * b6;
1094 t15 += v * b7;
1095 t16 += v * b8;
1096 t17 += v * b9;
1097 t18 += v * b10;
1098 t19 += v * b11;
1099 t20 += v * b12;
1100 t21 += v * b13;
1101 t22 += v * b14;
1102 t23 += v * b15;
1103 v = a[9];
1104 t9 += v * b0;
1105 t10 += v * b1;
1106 t11 += v * b2;
1107 t12 += v * b3;
1108 t13 += v * b4;
1109 t14 += v * b5;
1110 t15 += v * b6;
1111 t16 += v * b7;
1112 t17 += v * b8;
1113 t18 += v * b9;
1114 t19 += v * b10;
1115 t20 += v * b11;
1116 t21 += v * b12;
1117 t22 += v * b13;
1118 t23 += v * b14;
1119 t24 += v * b15;
1120 v = a[10];
1121 t10 += v * b0;
1122 t11 += v * b1;
1123 t12 += v * b2;
1124 t13 += v * b3;
1125 t14 += v * b4;
1126 t15 += v * b5;
1127 t16 += v * b6;
1128 t17 += v * b7;
1129 t18 += v * b8;
1130 t19 += v * b9;
1131 t20 += v * b10;
1132 t21 += v * b11;
1133 t22 += v * b12;
1134 t23 += v * b13;
1135 t24 += v * b14;
1136 t25 += v * b15;
1137 v = a[11];
1138 t11 += v * b0;
1139 t12 += v * b1;
1140 t13 += v * b2;
1141 t14 += v * b3;
1142 t15 += v * b4;
1143 t16 += v * b5;
1144 t17 += v * b6;
1145 t18 += v * b7;
1146 t19 += v * b8;
1147 t20 += v * b9;
1148 t21 += v * b10;
1149 t22 += v * b11;
1150 t23 += v * b12;
1151 t24 += v * b13;
1152 t25 += v * b14;
1153 t26 += v * b15;
1154 v = a[12];
1155 t12 += v * b0;
1156 t13 += v * b1;
1157 t14 += v * b2;
1158 t15 += v * b3;
1159 t16 += v * b4;
1160 t17 += v * b5;
1161 t18 += v * b6;
1162 t19 += v * b7;
1163 t20 += v * b8;
1164 t21 += v * b9;
1165 t22 += v * b10;
1166 t23 += v * b11;
1167 t24 += v * b12;
1168 t25 += v * b13;
1169 t26 += v * b14;
1170 t27 += v * b15;
1171 v = a[13];
1172 t13 += v * b0;
1173 t14 += v * b1;
1174 t15 += v * b2;
1175 t16 += v * b3;
1176 t17 += v * b4;
1177 t18 += v * b5;
1178 t19 += v * b6;
1179 t20 += v * b7;
1180 t21 += v * b8;
1181 t22 += v * b9;
1182 t23 += v * b10;
1183 t24 += v * b11;
1184 t25 += v * b12;
1185 t26 += v * b13;
1186 t27 += v * b14;
1187 t28 += v * b15;
1188 v = a[14];
1189 t14 += v * b0;
1190 t15 += v * b1;
1191 t16 += v * b2;
1192 t17 += v * b3;
1193 t18 += v * b4;
1194 t19 += v * b5;
1195 t20 += v * b6;
1196 t21 += v * b7;
1197 t22 += v * b8;
1198 t23 += v * b9;
1199 t24 += v * b10;
1200 t25 += v * b11;