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#
267 lines • 13 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { Field, Mina, Bytes, SmartContract, method, state, State, Poseidon, PrivateKey, AccountUpdate, UInt8, Bool, } from 'o1js';
import { Bigint2048 } from 'o1js-rsa';
import { generateInputs } from './generate-inputs.js';
import { emailVerify } from './email-verify.js';
import fs from 'fs';
const filePath = './eml/twitter.eml';
const rawEmail = fs.readFileSync(filePath, 'utf8');
const inputs = await generateInputs(rawEmail);
const computedHashPrint = Poseidon.hash(inputs.publicKey.fields);
console.log(computedHashPrint);
class HeadersBytes extends Bytes(1024) {
}
class BodyBytes extends Bytes(1536) {
}
class Twitter extends SmartContract {
constructor() {
super(...arguments);
this.TwitterPublicKeyHash = State();
// @method async updateTwitterPublicKeyHash () { }
}
init() {
super.init();
// Use the hash you computed earlier
const computedHash = Poseidon.hash(inputs.publicKey.fields);
this.TwitterPublicKeyHash.set(computedHash);
}
async verify_handle(paddedHeader, headerHashIndex, signature, publicKey, paddedBodyRemainingBytes, precomputedHash, bodyHashIndex, headerBodyHashIndex) {
// check public key has matches the stored:
const currentTwitterPublicKeyHash = await this.TwitterPublicKeyHash.get();
this.TwitterPublicKeyHash.requireEquals(this.TwitterPublicKeyHash.get());
const publickeyhash = Poseidon.hash(publicKey.fields);
publickeyhash.assertEquals(currentTwitterPublicKeyHash);
// email verify email signature with body hash check
emailVerify(paddedHeader, headerHashIndex, signature, publicKey, 1024, false, paddedBodyRemainingBytes, precomputedHash, bodyHashIndex, headerBodyHashIndex);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let { out, reveal } = twitterInputRegex(paddedHeader.bytes);
out.assertEquals(true, 'regex check fail. not a valid email');
}
}
__decorate([
state(Field),
__metadata("design:type", Object)
], Twitter.prototype, "TwitterPublicKeyHash", void 0);
__decorate([
method,
__metadata("design:type", Function),
__metadata("design:paramtypes", [HeadersBytes,
Field,
Bigint2048,
Bigint2048,
BodyBytes, Object, Field,
Field]),
__metadata("design:returntype", Promise)
], Twitter.prototype, "verify_handle", null);
const useProof = false;
const Local = await Mina.LocalBlockchain({ proofsEnabled: useProof });
Mina.setActiveInstance(Local);
const deployerAccount = Local.testAccounts[0];
const deployerKey = deployerAccount.key;
const senderAccount = Local.testAccounts[1];
const senderKey = senderAccount.key;
// ----------------------------------------------------
// Create a public/private key pair. The public key is your address and where you deploy the zkApp to
const zkAppPrivateKey = PrivateKey.random();
const zkAppAddress = zkAppPrivateKey.toPublicKey();
// create an instance of DKIMRegistry - and deploy it to zkAppAddress
const zkAppInstance = new Twitter(zkAppAddress);
const deployTxn = await Mina.transaction(deployerAccount, async () => {
AccountUpdate.fundNewAccount(deployerAccount);
await zkAppInstance.deploy();
});
await deployTxn.sign([deployerKey, zkAppPrivateKey]).send();
console.log('DKIMRegistry deployed');
// get the initial state of Square after deployment
const initialState = zkAppInstance.TwitterPublicKeyHash.get();
console.log('state after init:', initialState);
const txn1 = await Mina.transaction(deployerAccount, async () => {
await zkAppInstance.verify_handle(inputs.paddedHeader, inputs.headerHashIndex, inputs.signature, inputs.publicKey, inputs.paddedBodyRemainingBytes, inputs.precomputedHash, inputs.bodyHashIndex, inputs.headerBodyHashIndex);
});
await txn1.prove();
const pendingTx = txn1.sign([deployerKey]).send();
await pendingTx.wait();
// regex fuction ran with this commandL: npm run zk-regex 'password resets for @[A-Za-z0-9]+' '[A-Za-z0-9]+'
// npm run zk-regex 'This email was meant for @[A-Za-z0-9_]+' '["[A-Za-z0-9_]+"]'
function twitterInputRegex(input) {
const num_bytes = input.length;
let states = Array.from({ length: num_bytes + 1 }, () => []);
let state_changed = Array.from({ length: num_bytes }, () => Bool(false));
states[0][0] = Bool(true);
for (let i = 1; i < 28; i++) {
states[0][i] = Bool(false);
}
for (let i = 0; i < num_bytes; i++) {
const eq0 = input[i].value.equals(64);
const and0 = states[i][27].and(eq0);
states[i + 1][1] = and0;
state_changed[i] = state_changed[i].or(states[i + 1][1]);
const lt0 = new UInt8(65).lessThanOrEqual(input[i]);
const lt1 = input[i].lessThanOrEqual(90);
const and1 = lt0.and(lt1);
const lt2 = new UInt8(97).lessThanOrEqual(input[i]);
const lt3 = input[i].lessThanOrEqual(122);
const and2 = lt2.and(lt3);
const eq1 = input[i].value.equals(48);
const eq2 = input[i].value.equals(49);
const eq3 = input[i].value.equals(50);
const eq4 = input[i].value.equals(51);
const eq5 = input[i].value.equals(52);
const eq6 = input[i].value.equals(53);
const eq7 = input[i].value.equals(54);
const eq8 = input[i].value.equals(55);
const eq9 = input[i].value.equals(56);
const eq10 = input[i].value.equals(57);
const eq11 = input[i].value.equals(95);
let multi_or0 = Bool(false);
multi_or0 = multi_or0.or(and1);
multi_or0 = multi_or0.or(and2);
multi_or0 = multi_or0.or(eq1);
multi_or0 = multi_or0.or(eq2);
multi_or0 = multi_or0.or(eq3);
multi_or0 = multi_or0.or(eq4);
multi_or0 = multi_or0.or(eq5);
multi_or0 = multi_or0.or(eq6);
multi_or0 = multi_or0.or(eq7);
multi_or0 = multi_or0.or(eq8);
multi_or0 = multi_or0.or(eq9);
multi_or0 = multi_or0.or(eq10);
multi_or0 = multi_or0.or(eq11);
const and3 = states[i][1].and(multi_or0);
const and4 = states[i][2].and(multi_or0);
let multi_or1 = Bool(false);
multi_or1 = multi_or1.or(and3);
multi_or1 = multi_or1.or(and4);
states[i + 1][2] = multi_or1;
state_changed[i] = state_changed[i].or(states[i + 1][2]);
const eq12 = input[i].value.equals(84);
const and5 = states[i][0].and(eq12);
states[i + 1][3] = and5;
state_changed[i] = state_changed[i].or(states[i + 1][3]);
const eq13 = input[i].value.equals(104);
const and6 = states[i][3].and(eq13);
states[i + 1][4] = and6;
state_changed[i] = state_changed[i].or(states[i + 1][4]);
const eq14 = input[i].value.equals(105);
const and7 = states[i][4].and(eq14);
states[i + 1][5] = and7;
state_changed[i] = state_changed[i].or(states[i + 1][5]);
const eq15 = input[i].value.equals(115);
const and8 = states[i][5].and(eq15);
states[i + 1][6] = and8;
state_changed[i] = state_changed[i].or(states[i + 1][6]);
const eq16 = input[i].value.equals(32);
const and9 = states[i][6].and(eq16);
states[i + 1][7] = and9;
state_changed[i] = state_changed[i].or(states[i + 1][7]);
const eq17 = input[i].value.equals(101);
const and10 = states[i][7].and(eq17);
states[i + 1][8] = and10;
state_changed[i] = state_changed[i].or(states[i + 1][8]);
const eq18 = input[i].value.equals(109);
const and11 = states[i][8].and(eq18);
states[i + 1][9] = and11;
state_changed[i] = state_changed[i].or(states[i + 1][9]);
const eq19 = input[i].value.equals(97);
const and12 = states[i][9].and(eq19);
states[i + 1][10] = and12;
state_changed[i] = state_changed[i].or(states[i + 1][10]);
const and13 = states[i][10].and(eq14);
states[i + 1][11] = and13;
state_changed[i] = state_changed[i].or(states[i + 1][11]);
const eq20 = input[i].value.equals(108);
const and14 = states[i][11].and(eq20);
states[i + 1][12] = and14;
state_changed[i] = state_changed[i].or(states[i + 1][12]);
const and15 = states[i][12].and(eq16);
states[i + 1][13] = and15;
state_changed[i] = state_changed[i].or(states[i + 1][13]);
const eq21 = input[i].value.equals(119);
const and16 = states[i][13].and(eq21);
states[i + 1][14] = and16;
state_changed[i] = state_changed[i].or(states[i + 1][14]);
const and17 = states[i][14].and(eq19);
states[i + 1][15] = and17;
state_changed[i] = state_changed[i].or(states[i + 1][15]);
const and18 = states[i][15].and(eq15);
states[i + 1][16] = and18;
state_changed[i] = state_changed[i].or(states[i + 1][16]);
const and19 = states[i][16].and(eq16);
states[i + 1][17] = and19;
state_changed[i] = state_changed[i].or(states[i + 1][17]);
const and20 = states[i][17].and(eq18);
states[i + 1][18] = and20;
state_changed[i] = state_changed[i].or(states[i + 1][18]);
const and21 = states[i][18].and(eq17);
states[i + 1][19] = and21;
state_changed[i] = state_changed[i].or(states[i + 1][19]);
const and22 = states[i][19].and(eq19);
states[i + 1][20] = and22;
state_changed[i] = state_changed[i].or(states[i + 1][20]);
const eq22 = input[i].value.equals(110);
const and23 = states[i][20].and(eq22);
states[i + 1][21] = and23;
state_changed[i] = state_changed[i].or(states[i + 1][21]);
const eq23 = input[i].value.equals(116);
const and24 = states[i][21].and(eq23);
states[i + 1][22] = and24;
state_changed[i] = state_changed[i].or(states[i + 1][22]);
const and25 = states[i][22].and(eq16);
states[i + 1][23] = and25;
state_changed[i] = state_changed[i].or(states[i + 1][23]);
const eq24 = input[i].value.equals(102);
const and26 = states[i][23].and(eq24);
states[i + 1][24] = and26;
state_changed[i] = state_changed[i].or(states[i + 1][24]);
const eq25 = input[i].value.equals(111);
const and27 = states[i][24].and(eq25);
states[i + 1][25] = and27;
state_changed[i] = state_changed[i].or(states[i + 1][25]);
const eq26 = input[i].value.equals(114);
const and28 = states[i][25].and(eq26);
states[i + 1][26] = and28;
state_changed[i] = state_changed[i].or(states[i + 1][26]);
const and29 = states[i][26].and(eq16);
states[i + 1][27] = and29;
state_changed[i] = state_changed[i].or(states[i + 1][27]);
states[i + 1][0] = state_changed[i].not();
}
let final_state_result = Bool(false);
for (let i = 0; i <= num_bytes; i++) {
final_state_result = final_state_result.or(states[i][2]);
}
const out = final_state_result;
const msg_bytes = num_bytes - 1;
const is_consecutive = Array.from({ length: num_bytes }, () => []);
is_consecutive[msg_bytes][1] = Bool(true);
for (let i = 0; i < msg_bytes; i++) {
is_consecutive[msg_bytes - 1 - i][0] = states[num_bytes - i][2]
.and(is_consecutive[msg_bytes - i][1].not())
.or(is_consecutive[msg_bytes - i][1]);
is_consecutive[msg_bytes - 1 - i][1] = state_changed[msg_bytes - i].and(is_consecutive[msg_bytes - 1 - i][0]);
}
// revealed transitions: [[[1,2],[2,2]]]
let reveal = [];
// the 0-th substring transitions: [[1,2],[2,2]]
const is_reveal0 = [];
let is_substr0 = Array.from({ length: msg_bytes }, () => []);
const reveal0 = [];
for (let i = 0; i < msg_bytes; i++) {
is_substr0[i][0] = Bool(false);
is_substr0[i][1] = is_substr0[i][0].or(states[i + 1][1].and(states[i + 2][2]));
is_substr0[i][2] = is_substr0[i][1].or(states[i + 1][2].and(states[i + 2][2]));
is_reveal0[i] = is_substr0[i][2].and(is_consecutive[i][1]);
reveal0[i] = input[i + 1].value.mul(is_reveal0[i].toField());
}
reveal.push(reveal0);
return { out, reveal };
}
//# sourceMappingURL=zkapp.js.map