UNPKG

o1js-email-verify

Version:

Implemented using [o1js](https://github.com/o1-labs/o1js), this project is a reimplementation of [zk-email](https://github.com/zkemail/zk-email-verify), leveraging the Mina proving system [Kimchi](https://o1-labs.github.io/proof-systems/specs/kimchi.html#

123 lines 5.63 kB
import { Field, Bytes } from 'o1js'; import { bodyHashRegex, selectSubarray } from './utils'; import fs from 'fs'; import { verifyDKIMSignature } from '@zk-email/helpers/dist/dkim'; /** * Converts an array of UTF-8 encoded bytes (represented as bigints) into a string. * * This function handles single-byte (ASCII), two-byte, three-byte, and four-byte UTF-8 characters. * It processes the bytes, constructs the corresponding Unicode code points, and converts them into a string. * * @param bytes - An array of bigints representing UTF-8 encoded bytes. * @returns The decoded string from the UTF-8 bytes. */ function utf8BytesToString(bytes) { let utf8String = ''; let codepoint = 0; let remainingBytes = 0; for (const byte of bytes.map(Number)) { if (remainingBytes === 0) { if (byte <= 0x7f) { // Single byte character (ASCII) utf8String += String.fromCharCode(Number(byte)); } else if (byte >= 0xc0 && byte <= 0xdf) { // Two byte character codepoint = byte & 0x1f; remainingBytes = 1; } else if (byte >= 0xe0 && byte <= 0xef) { // Three byte character codepoint = byte & 0x0f; remainingBytes = 2; } else if (byte >= 0xf0 && byte <= 0xf7) { // Four byte character codepoint = byte & 0x07; remainingBytes = 3; } } else { // Continuation byte codepoint = (codepoint << 6) | (byte & 0x3f); remainingBytes--; if (remainingBytes === 0) { utf8String += String.fromCharCode(codepoint); } } } return utf8String; } /** * Tests the `bodyHashRegex` function by verifying the count of matching patterns * and the revealed substring, if provided. * * @param input - The input string to be tested. * @param expectedCount - The expected count of matching patterns. * @param expectedSubstring - Optional. The expected substring to be revealed by the regex. */ function testBodyHashRegex(input, expectedCount, expectedSubstring) { const inputBytes = Bytes.fromString(input).bytes; const { out, reveal } = bodyHashRegex(inputBytes); if (expectedSubstring) { const revealedBytes = reveal[0] .map((f) => f.toBigInt()) .filter((byte) => byte !== 0n); const revealedSubString = utf8BytesToString(revealedBytes); expect(revealedSubString).toEqual(expectedSubstring); } expect(out).toEqual(Field(expectedCount)); } describe('Body Hash Regex Tests', () => { let emailString; beforeAll(async () => { emailString = fs.readFileSync('./eml/email-good.eml', 'utf8'); }); it('should reveal the correct body hash from an eml file', () => { testBodyHashRegex(emailString, 1, 'aeLbTnlUQQv2UFEWKHeiL5Q0NjOwj4ktNSInk8rN/P0='); }); it('should reveal the correct body hash from a random string', () => { const input = 'This is one example of email headers that contain\ bh=7xQMDuoVVU4m0W0WRVSrVXMeGSIASsnucK9dJsrc+vU=;\ that is acceped'; testBodyHashRegex(input, 1, '7xQMDuoVVU4m0W0WRVSrVXMeGSIASsnucK9dJsrc+vU='); }); it('should reject an input with two matching body hash patterns', () => { let input = emailString + 'bh=2JsdK4BMzzt9w4Zlz2TdyVCFc+l7vNyT5aAgGDYf7fM=;'; expect(() => testBodyHashRegex(input, 1)).toThrow(); }); it('should reject input that does not match the body hash pattern - case 1', () => { const input = '=2JsdK4BMzzt9w4Zlz2TdyVCFc+l7vNyT5aAgGDYf7fM=;'; expect(() => testBodyHashRegex(input, 1)).toThrow(); }); it('should reject input that does not match the body hash pattern - case 2', () => { const input = '2JsdK4BMzzt9w4Zlz2TdyVCFc+l7vNyT5aAgGDYf7fM=;'; expect(() => testBodyHashRegex(input, 1)).toThrow(); }); it('should reject input that does not match the body hash pattern - case 3', () => { const input = 'b=2JsdK4BMzzt9w4Zlz2TdyVCFc+l7vNyT5aAgGDYf7fM=;'; expect(() => testBodyHashRegex(input, 1)).toThrow(); }); it('should reject input that does not match the body hash pattern - case 4', () => { const input = 'h=2JsdK4BMzzt9w4Zlz2TdyVCFc+l7vNyT5aAgGDYf7fM=;'; expect(() => testBodyHashRegex(input, 1)).toThrow(); }); it('should reject input that does not match the body hash pattern - case 5', () => { const input = 'bh=2JsdK4BMzzt9w4Zlz2TdyVCFc+l7vNyT5aAgGDYf7fM='; expect(() => testBodyHashRegex(input, 1)).toThrow(); }); it('should reject input with non Base64 body hash', () => { const input = 'bh=2JsdK4BMz#zt9w4Zlz2TdyVCFc+l7vNyT5aAgGDYf7fM='; expect(() => testBodyHashRegex(input, 1)).toThrow(); }); it('should select the correct revealed body hash bytes from an eml file', async () => { const { headers, bodyHash } = await verifyDKIMSignature(Buffer.from(emailString)); const { out, reveal } = bodyHashRegex(Bytes.from(headers).bytes); expect(out).toEqual(Field(1)); const bodyHashIndex = headers.toString().indexOf(bodyHash) - 1; const selectedBodyHashBytes = selectSubarray(reveal[0], Field(bodyHashIndex), 44); const revealedBodyHash = utf8BytesToString(selectedBodyHashBytes.map((x) => x.toBigInt())); expect(revealedBodyHash).toEqual('aeLbTnlUQQv2UFEWKHeiL5Q0NjOwj4ktNSInk8rN/P0='); }); }); //# sourceMappingURL=regex.test.js.map