diff --git a/core/sia-share.js b/core/sia-share.js index a2e0e4e..b642f31 100644 --- a/core/sia-share.js +++ b/core/sia-share.js @@ -58,13 +58,63 @@ export function hchacha20(key, nonce16) { [0, 1, 2, 3, 12, 13, 14, 15].forEach((w, i) => out.writeUInt32LE(s[w], i * 4)); return out; } +// ChaCha20 and Poly1305 in plain JS (RFC 8439): Electron's BoringSSL has no +// chacha20 or chacha20-poly1305 in createCipheriv ("Unknown cipher" in the +// Theseus add-on), and all this ever opens is a 32-byte key. +function chachaBlock(key, counter, nonce12) { + const s = new Uint32Array(16); + s.set([0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]); + for (let i = 0; i < 8; i++) s[4 + i] = key.readUInt32LE(i * 4); + s[12] = counter >>> 0; + for (let i = 0; i < 3; i++) s[13 + i] = nonce12.readUInt32LE(i * 4); + const w = Uint32Array.from(s); + for (let r = 0; r < 10; r++) { + quarter(w, 0, 4, 8, 12); quarter(w, 1, 5, 9, 13); quarter(w, 2, 6, 10, 14); quarter(w, 3, 7, 11, 15); + quarter(w, 0, 5, 10, 15); quarter(w, 1, 6, 11, 12); quarter(w, 2, 7, 8, 13); quarter(w, 3, 4, 9, 14); + } + const out = Buffer.alloc(64); + for (let i = 0; i < 16; i++) out.writeUInt32LE((w[i] + s[i]) >>> 0, i * 4); + return out; +} + +function chachaXor(key, counter, nonce12, data) { + const out = Buffer.alloc(data.length); + for (let off = 0, c = counter; off < data.length; off += 64, c++) { + const ks = chachaBlock(key, c, nonce12); + for (let i = 0; i < 64 && off + i < data.length; i++) out[off + i] = data[off + i] ^ ks[i]; + } + return out; +} + +function poly1305(key32, msg) { + const le = (b) => b.reduceRight((acc, x) => (acc << 8n) | BigInt(x), 0n); + const r = le(key32.subarray(0, 16)) & 0x0ffffffc0ffffffc0ffffffc0fffffffn; + const s = le(key32.subarray(16, 32)); + const p = (1n << 130n) - 5n; + let acc = 0n; + for (let off = 0; off < msg.length; off += 16) { + const block = msg.subarray(off, off + 16); + acc = ((acc + le(block) + (1n << BigInt(8 * block.length))) * r) % p; + } + acc = (acc + s) & ((1n << 128n) - 1n); + const tag = Buffer.alloc(16); + for (let i = 0; i < 16; i++) { tag[i] = Number(acc & 0xffn); acc >>= 8n; } + return tag; +} + +const pad16 = (n) => Buffer.alloc((16 - (n % 16)) % 16); +const le64 = (n) => { const b = Buffer.alloc(8); b.writeBigUInt64LE(BigInt(n)); return b; }; + export function xchachaOpen(key, nonce24, sealed) { const sub = hchacha20(Buffer.from(key), Buffer.from(nonce24).subarray(0, 16)); const nonce12 = Buffer.concat([Buffer.alloc(4), Buffer.from(nonce24).subarray(16, 24)]); const ct = Buffer.from(sealed); - const d = crypto.createDecipheriv('chacha20-poly1305', sub, nonce12, { authTagLength: 16 }); - d.setAuthTag(ct.subarray(ct.length - 16)); - return Buffer.concat([d.update(ct.subarray(0, ct.length - 16)), d.final()]); + if (ct.length < 16) throw new Error('sealed data is too short'); + const body = ct.subarray(0, ct.length - 16); + const polyKey = chachaBlock(sub, 0, nonce12).subarray(0, 32); + const want = poly1305(polyKey, Buffer.concat([body, pad16(body.length), le64(0), le64(body.length)])); + if (!crypto.timingSafeEqual(want, ct.subarray(ct.length - 16))) throw new Error('unable to authenticate data'); + return chachaXor(sub, 1, nonce12, body); } // ---- s3d's database: / -> Sia object id -------------------------------