diff --git a/package.json b/package.json index b30f722..a5fe77d 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,12 @@ "jest": "^27.3.1", "lint-staged": "^11.0.1", "prettier": "^2.3.2", + "snarkyjs": "^0.11.3", "ts-jest": "^27.0.7", - "typescript": "^4.7.2", - "snarkyjs": "0.10.*" + "typescript": "^4.7.2" + }, + "dependencies": { + "snarkyjs-math": "^0.3.0", + "zod": "^3.21.4" } } diff --git a/src/Pepe.ts b/src/Pepe.ts new file mode 100644 index 0000000..a66b38c --- /dev/null +++ b/src/Pepe.ts @@ -0,0 +1,58 @@ +import { Field, SmartContract, state, State, method, Bool } from 'snarkyjs'; +import { CircuitMath, CircuitNumber } from 'snarkyjs-math'; +import { PepeStruct } from './generated/PepeStruct.js'; + +export class Pepe extends SmartContract { + @state(PepeStruct) pep = State(); + + init() { + super.init(); + + const p = new PepeStruct( + new Field(0), + new Field(0), + new Field(0) + ); + + this.pep.set(p) + } + + @method test(): Bool { + const currentState: PepeStruct = this.pep.getAndAssertEquals(); + + return new Bool(true) + } + + @method updateIssuer(issuer: Field) { + const pep: PepeStruct = this.pep.getAndAssertEquals(); + pep.issuer = issuer + this.pep.set(pep) + } + + + @method updateExpiration(expiration: Field) { + const pep: PepeStruct = this.pep.getAndAssertEquals(); + pep.expiration = expiration + this.pep.set(pep) + } + + @method updateAccount(account: Field) { + const pep: PepeStruct = this.pep.getAndAssertEquals(); + // pep.account = account + // this.pep.set(pep) + } + + + + @method data(): [Field, Field, Field] { + const pep: PepeStruct = this.pep.getAndAssertEquals(); + + return [pep.issuer, pep.expiration, pep.account] + } + + @method f(): Field { + const pep: PepeStruct = this.pep.getAndAssertEquals(); + + return pep._field0 + } +} diff --git a/src/generated/PepeStruct.ts b/src/generated/PepeStruct.ts new file mode 100644 index 0000000..81809ea --- /dev/null +++ b/src/generated/PepeStruct.ts @@ -0,0 +1,162 @@ +import { + Field, + SmartContract, + state, + State, + method, + Poseidon, + Bool, + Struct, + Circuit, + UInt64, + UInt32, + Provable, +} from 'snarkyjs'; +import { + CircuitMath, + CircuitNumber, +} from 'snarkyjs-math/build/src/snarkyjs-math'; + +const ISSUER_OFFSET = new Field(1); +const ISSUER_MASK = new Field(2 ** 16); + +const EXPIRATION_OFFSET = new Field(2 ** 16); +const EXPIRATION_MASK = new Field(2 ** 40); + +// const ACCOUNT_OFFSET = new Field(2 ** 56); +// const ACCOUNT_MASK = new Field(2 ** 63); + + +export class PepeStruct extends Struct({ + _field0: Field, +}) { + constructor(issuer: Field, expiration: Field, account: Field) { + super({ + _field0: PepeStruct._initField0(issuer, expiration, account), + }); + + this.check(); + } + public check() {} + + get issuer(): Field { + // return this._get(this._field0, UInt64.from(ISSUER_OFFSET.toBigInt()), UInt64.from(ISSUER_MASK.toBigInt()) + return this._get(this._field0, ISSUER_OFFSET, ISSUER_MASK); + // return new Field(1) + } + + get expiration(): Field { + // return this._get(this._field0, UInt64.from(EXPIRATION_OFFSET.toBigInt()), UInt64.from(EXPIRATION_MASK.toBigInt()) + return this._get(this._field0, EXPIRATION_OFFSET, EXPIRATION_MASK); + return new Field(2) + } + + get account(): Field { + // return _get160(this._field0, ACCOUNT_OFFSET, ACCOUNT_MASK); + return new Field(3) + } + + set issuer(value: Field) { + // this._field0 = this._set(this._field0, value, UInt64.from(ISSUER_OFFSET), UInt64.from(ISSUER_MASK)) + this._field0 = this._set(this._field0, value, ISSUER_OFFSET, ISSUER_MASK); + } + + set expiration(value: Field) { + this._field0 = this._set(this._field0, value, EXPIRATION_OFFSET, EXPIRATION_MASK); + } + // set account(value: Field) { + // this._field0 = _set160(this._field0, value, ACCOUNT_MASK, EXPIRATION_MASK); + // } + + static _initField0(issuer: Field, expiration: Field, account: Field): Field { + const value = new Field( + issuer.mul(ISSUER_OFFSET).add(expiration.mul(EXPIRATION_OFFSET)) + // r.add(account.mul(ACCOUNT_OFFSET)) + ); + return value; + } + + private _get(currentValue: Field, offset: Field, size: Field): Field { + // console.log(currentValue.toString()) + // console.log(`_get`) + const r = Provable.witness(Field, () => { + // const dirtyValue = UInt64.from(value).div(offset); + // return dirtyValue.mod(size).toFields()[0]; + + + const dirtyValue = UInt64.from(currentValue).div(UInt64.from(offset)); + // console.log(`dirty value: ${UInt64.fromcurrentValue.toBigInt()} / ${offset.toBigInt()} = ${dirtyValue.toBigInt()}`); + // q = 0xffdead / x10000 => 0xff + const q = dirtyValue.div(UInt64.from(size)); + // console.log(`q: ${currentValue.toBigInt()} / ${size.toBigInt()} = ${q.toBigInt()}`); + // rest = 0xffdead % 0x10000 <=> r = 0xffdead - (q * 0x10000) => 0xdead + const p = q.mul(UInt64.from(size)) + // console.log(`q * size: ${q.toBigInt()} * ${size.toBigInt()} = ${p.toBigInt()}`); + const value = dirtyValue.sub(p); + return value.toFields()[0] + // console.log(`value: ${dirtyValue.toBigInt()} - ${p.toBigInt()} = ${value.toBigInt()}`); + return dirtyValue.toFields()[0] + }); + + return r; + } + private _get160( + currentValue: Field, + offset: Field, + size: Field + ): Field { + const r = Provable.witness(Field, () => { + // 0xffdeadff / 0x100 => 0xdead + const dirtyValue = currentValue.div(offset); + // q = 0xffdead / x10000 => 0xff + const q = dirtyValue.div(size); + + // rest = 0xffdead % 0x10000 <=> r = 0xffdead - (q * 0x10000) => 0xdead + const rest = dirtyValue.sub(q.mul(size)); + rest.assertLessThan(dirtyValue); + return rest; + // return dirtyValue.mod(size).toFields()[0]; + }); + + return r; + } + + // console.log(`setting new ${value.toBigInt()} in offset ${offset.toBigInt()}`) + private _set( + currentField: Field, + value: Field, + offset: Field, + size: Field + ): Field { + // console.log(`_set`) + + // const shiftedValue = new Field(UInt64.from(value).mul(offset).toBigInt()) + const newField = Provable.witness(Field, () => { + + const shiftedValue = value.mul(offset); + // console.log(`shifted: ${value.toBigInt()} * ${offset.toBigInt()} = ${shiftedValue.toBigInt()}`); + const currentValue = this._get(currentField, offset, size); + // console.log(`current: ${currentValue.toBigInt()}`); + // const newField = currentField.sub(currentValue).add(shiftedValue); + const newValue = currentField.sub(currentValue).add(shiftedValue); + // console.log(`new: ${newValue.toBigInt()}`); + return newValue; + }) + return newField; + } + + private _set160( + currentField: Field, + newValue: Field, + offset: Field, + size: Field + ): Field { + // const shiftedValue = new Field(UInt64.from(value).mul(offset).toBigInt()) + const shiftedValue = newValue.mul(offset); + const currentValue = this._get160(currentField, offset, size); + const newField = currentField.sub(currentValue).add(shiftedValue); + return newField; + } + + +} diff --git a/src/pepe.test.ts b/src/pepe.test.ts new file mode 100644 index 0000000..8450728 --- /dev/null +++ b/src/pepe.test.ts @@ -0,0 +1,156 @@ +import { + Field, + Mina, + PrivateKey, + PublicKey, + AccountUpdate, + Bool, + Circuit, + Provable, + fetchAccount, +} from 'snarkyjs'; +import { + CircuitMath, + CircuitNumber, +} from 'snarkyjs-math/build/src/snarkyjs-math.js'; +import { ZkappCommand } from 'snarkyjs/dist/node/lib/account_update.js'; +import { PepeStruct } from './generated/PepeStruct.js'; +import { Pepe } from './Pepe.js'; + +describe('pepe.js', () => { + let deployerAccount: PublicKey, + senderAccount: PublicKey, + zkAppPrivateKey: PrivateKey, + zkAppAddress: PublicKey, + deployerKey: PrivateKey, + senderKey: PrivateKey, + zkApp: Pepe; + + beforeAll(async () => { + const useProof = false; + + const Local = Mina.LocalBlockchain({ proofsEnabled: useProof }); + Mina.setActiveInstance(Local); + // const { privateKey: deployerKey, publicKey: deployerAccount } = Local.testAccounts[0]; + // const { privateKey: senderKey, publicKey: senderAccount } = Local.testAccounts[1]; + deployerKey = Local.testAccounts[0].privateKey + deployerAccount = Local.testAccounts[0].publicKey + senderKey = Local.testAccounts[1].privateKey + senderAccount = Local.testAccounts[1].publicKey + + // ---------------------------------------------------- + + // Create a public/private key pair. The public key is your address and where you deploy the zkApp to + zkAppPrivateKey = PrivateKey.random(); + zkAppAddress = zkAppPrivateKey.toPublicKey(); + zkApp = new Pepe(zkAppAddress); + + console.log('compiling') + await Pepe.compile(); + + await fetchAccount({ publicKey: zkAppAddress }); + + zkApp = new Pepe(zkAppAddress); + + const deployTxn = await Mina.transaction(deployerAccount, () => { + AccountUpdate.fundNewAccount(deployerAccount); + zkApp.deploy(); + }); + await deployTxn.sign([deployerKey, zkAppPrivateKey]).send(); + }); + beforeEach(async () => { + + + }) + + describe('updates field()', () => { + it('can set issuer', async () => { + const issuer = 0xf344 + + let updateTx = await Mina.transaction(senderAccount, () => { + // zkApp.updateField(new Field(packed)); + zkApp.updateIssuer(new Field(issuer)) + }); + + await updateTx.prove() + await updateTx.sign([senderKey]).send(); + + const pep = zkApp.pep.get() + + expect(pep.issuer).toEqual(Field.from(issuer)); + + }); + it('can set expiration', async () => { + const expiration = 0xffaabb + + let updateTx = await Mina.transaction(senderAccount, () => { + // zkApp.updateField(new Field(packed)); + zkApp.updateExpiration(new Field(expiration)) + }); + + await updateTx.prove() + await updateTx.sign([senderKey]).send(); + + let pep = zkApp.pep.get() + expect(pep.expiration).toEqual(Field.from(expiration)); + }); + + it('can update issuer', async () => { + const issuer1 = 0xf344 + + let updateTx = await Mina.transaction(senderAccount, () => { + zkApp.updateIssuer(new Field(issuer1)) + }); + + await updateTx.prove() + await updateTx.sign([senderKey]).send(); + + expect(zkApp.pep.get().issuer).toEqual(Field.from(issuer1)); + + const issuer2 = 0xcaca + updateTx = await Mina.transaction(senderAccount, () => { + zkApp.updateIssuer(new Field(issuer2)) + }); + + await updateTx.prove() + await updateTx.sign([senderKey]).send(); + expect(zkApp.pep.get().issuer).toEqual(Field.from(issuer2)); + + + + }) + it('can set all variables', async () => { + + const issuer = 0xfeaa + const expiration = 0xffdead + + let updateTx = await Mina.transaction(senderAccount, () => { + // zkApp.updateField(new Field(packed)); + zkApp.updateIssuer(new Field(issuer)) + // zkApp.updateExpiration(new Field(expiration)) + }); + await updateTx.prove() + await updateTx.sign([senderKey]).send(); + + let pep = zkApp.pep.get() + expect(pep.issuer).toEqual(Field.from(issuer)); + updateTx = await Mina.transaction(senderAccount, () => { + // zkApp.updateField(new Field(packed)); + zkApp.updateExpiration(new Field(expiration)) + }); + await updateTx.prove() + await updateTx.sign([senderKey]).send(); + + pep = zkApp.pep.get() + console.log(pep.issuer.toString(), pep.expiration.toString()) + + const expectedField = (expiration * (2 ** 16)) + issuer + expect(pep.expiration).toEqual(Field.from(expiration)); + expect(pep._field0.toString()).toEqual(expectedField.toString()) + + + }) + it.skip('can set account', async () => { + }); + }); +}); diff --git a/src/script.ts b/src/script.ts new file mode 100644 index 0000000..609ff65 --- /dev/null +++ b/src/script.ts @@ -0,0 +1,91 @@ +import { + Field, + Mina, + PrivateKey, + AccountUpdate, + fetchAccount, +} from 'snarkyjs'; +import { Pepe } from './Pepe.js'; + +const useProof = false; + +const Local = Mina.LocalBlockchain({ proofsEnabled: useProof }); +Mina.setActiveInstance(Local); +const { privateKey: deployerKey, publicKey: deployerAccount } = Local.testAccounts[0]; +const { privateKey: senderKey, publicKey: senderAccount } = Local.testAccounts[1]; + +const zkAppPrivateKey = PrivateKey.random(); +const zkAppAddress = zkAppPrivateKey.toPublicKey(); +const zkApp = new Pepe(zkAppAddress); + +console.log('compiling') +await Pepe.compile(); + +await fetchAccount({ publicKey: zkAppAddress }); + +const deployTxn = await Mina.transaction(deployerAccount, () => { + AccountUpdate.fundNewAccount(deployerAccount); + zkApp.deploy(); +}); +await deployTxn.sign([deployerKey, zkAppPrivateKey]).send(); + +console.log('deployed') + +let pep = zkApp.pep.get() + +// console.log(pep.issuer.toString(), pep.expiration.toString()) + +// console.log(pep._field0.toString()) + +// update state + +const issuer = 0xf344 +const expiration = 0xffaabb + +let expectedField = (expiration * (2 ** 16)) + issuer + +let updateTx = await Mina.transaction(senderAccount, () => { + // zkApp.updateField(new Field(packed)); + zkApp.updateIssuer(new Field(issuer)) +}); + +console.log('proving updateIssuer...') +await updateTx.prove() +await updateTx.sign([senderKey]).send(); + +pep = zkApp.pep.get() +// console.log('field value:', pep._field0.toString()) +// console.log(zkApp.data().toString()) +console.log('issuer:', pep.issuer.toString()) + +updateTx = await Mina.transaction(senderAccount, () => { + // zkApp.updateField(new Field(packed)); + zkApp.updateExpiration(new Field(expiration)) +}); + +console.log('proving updateExpiration...') +await updateTx.prove() +await updateTx.sign([senderKey]).send(); + +pep = zkApp.pep.get() +console.log('expected field value:', pep._field0.toString() == expectedField.toString()) +console.log('expected field value:', pep._field0.toString()) +// console.log(zkApp.data().toString()) +console.log('expiration', pep.expiration.toString()) + +// updateTx = await Mina.transaction(senderAccount, () => { +// // zkApp.updateField(new Field(packed)); + +// zkApp.updateAccount(new Field(0x388c818ca8b9251b393131c08a736a67ccb19297)) +// }); + +// console.log('proving updateAccount...') +// await updateTx.prove() +// await updateTx.sign([senderKey]).send(); + +// pep = zkApp.pep.get() +// console.log(pep.account.toString()) +// console.log(pep._field0.toString()) +// // console.log('expected field value:', pep.account.toString() == "0x388c818ca8b9251b393131c08a736a67ccb19297") +// console.log(zkApp.data().toString()) +