Skip to content

Commit

Permalink
Implements the signature verification inside a Mina smart contract.
Browse files Browse the repository at this point in the history
  • Loading branch information
racampos committed Apr 13, 2024
1 parent 389ade4 commit 887ea5b
Show file tree
Hide file tree
Showing 10 changed files with 6,352 additions and 188 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
"o1js": "0.17.*"
},
"dependencies": {
"@mysten/bcs": "^0.11.1",
"json-stable-stringify": "^1.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
19 changes: 0 additions & 19 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions src/SessionHeader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
Field,
Provable,
Struct,
} from 'o1js';

export class SessionHeader extends Struct ({
encoderSeed: Provable.Array(Field, 32),
merkleRoot: Provable.Array(Field, 32),
sentLen: Provable.Array(Field, 8),
recvLen: Provable.Array(Field, 8),
handshakeSummary: Struct({
time: Provable.Array(Field, 8),
serverPublicKey: Struct({
group: Provable.Array(Field, 2),
key: Provable.Array(Field, 65),
}),
handshakeCommitment: Provable.Array(Field, 32),
}),
}) {
toFields(): Field[] {
return [
...this.encoderSeed,
...this.merkleRoot,
...this.sentLen,
...this.recvLen,
...this.handshakeSummary.time,
...this.handshakeSummary.serverPublicKey.group,
...this.handshakeSummary.serverPublicKey.key,
...this.handshakeSummary.handshakeCommitment,
];
}
}
57 changes: 1 addition & 56 deletions src/TlsnVerifier.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ import { z } from 'zod';
import { RootSchema } from './schemas.js';
import { p } from 'o1js/dist/node/bindings/crypto/finite-field.js';

import { bcs } from '@mysten/bcs';

import stableStringify from 'json-stable-stringify';

let count = 0;
Expand Down Expand Up @@ -67,61 +65,8 @@ describe('TlsnVerifier.js', () => {

console.log('count: ', count++);

// ----------------------------------------------------
// Serialize the header object in a deterministic manner

const Header = bcs.struct('Header', {
encoder_seed: bcs.fixedArray(32, bcs.u8()),
merkle_root: bcs.fixedArray(32, bcs.u8()),
sent_len: bcs.u64(),
recv_len: bcs.u64(),
handshake_summary: bcs.struct('handshake_summary', {
time: bcs.u64(),
server_public_key: bcs.struct('server_public_key', {
group: bcs.fixedArray(2, bcs.u8()),
key: bcs.fixedArray(65, bcs.u8()),
}),
handshake_commitment: bcs.fixedArray(32, bcs.u8()),
}),
});

console.log('count: ', count++);

const header = Header.serialize(tlsnProof.session.header);
console.log('count: ', count++);

const headerBytes = header.toBytes();

console.log('count: ', count++);

const headerFields: Field[] = [];

headerBytes.forEach((byte: number) => headerFields.push(Field(byte)));

console.log('count: ', count++);

// // Define the transform for the Signature
// const sdsabhjdsabhjdsa = bcs.fixedArray(32, bcs.u8()).transform({
// input: (val: number[]) => val.map((byte) => Field(byte)),
// output: (val) => val.map((field) => field.toNumber()),
// });

// const signatureBytes = tlsnProof.session.signature.map((byte: number) =>
// Field(byte)
// );

// console.log('count: ', count++);

// const signature = Signature.fromFields(signatureBytes);

// console.log('count: ', count++);

// const txn1 = await Mina.transaction(senderAccount, () => {
// zkAppInstance.verify(headerFields, signature);
// });
// await txn1.prove();

// await txn1.sign([senderKey]).send();

} else {
// Data is invalid
console.error('Invalid data:', result.error);
Expand Down
9 changes: 5 additions & 4 deletions src/TlsnVerifier.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import {
Field,
SmartContract,
state,
State,
method,
Signature,
PublicKey
PublicKey,
} from 'o1js';

import { SessionHeader } from './SessionHeader.js';

export class TlsnVerifier extends SmartContract {
@state(PublicKey) notaryPublicKey = State<PublicKey>();

@method verify(
sessionHeader: Field[],
sessionHeader: SessionHeader,
signature: Signature
) {
// Get the notary public key from the contract state
const notaryPublicKey = this.notaryPublicKey.getAndRequireEquals();

// Evaluate whether the signature is valid for the provided data
const validSignature = signature.verify(notaryPublicKey, sessionHeader);
const validSignature = signature.verify(notaryPublicKey, sessionHeader.toFields());
validSignature.assertTrue();
}
}
97 changes: 50 additions & 47 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,86 +14,89 @@ import { TlsnVerifier } from './TlsnVerifier.js';
import fs from 'fs';
import { z } from 'zod';
import { RootSchema } from './schemas.js';
import { p } from 'o1js/dist/node/bindings/crypto/finite-field.js';

// import { bcs } from '@mysten/bcs';
import { SessionHeader } from './SessionHeader.js';

// Convert a number to a byte array
function numberToBytes(num: number) {
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setInt32(0, num, true);
return new Uint8Array(buffer);
}

import stableStringify from 'json-stable-stringify';
function bytesToFields(bytes: Uint8Array): Field[] {
const fields: Field[] = [];
bytes.forEach((byte: number) => fields.push(Field(byte)));
return fields;
}

// Read the JSON file
const jsonData = fs.readFileSync('src/simple_proof.json', 'utf-8');

// Parse the JSON data
const parsedData = JSON.parse(jsonData);
// Replace the string "secp256r1" for its corresponding byte representation.
parsedData.session.header.handshake_summary.server_public_key.group = [0, 65];

// Validate the parsed data using Zod
const result = RootSchema.safeParse(parsedData);

if (result.success) {
// Data is valid
const tlsnProof = result.data;
console.log('Valid data:', tlsnProof);
const signature = Signature.fromBase58(tlsnProof.session.signature);

const encoder_seed = tlsnProof.session.header.encoder_seed;
const merkle_root = tlsnProof.session.header.merkle_root;
const sent_len = numberToBytes(tlsnProof.session.header.sent_len);
const recv_len = numberToBytes(tlsnProof.session.header.recv_len);
const time = numberToBytes(tlsnProof.session.header.handshake_summary.time);
const group = tlsnProof.session.header.handshake_summary.server_public_key.group;
const key = tlsnProof.session.header.handshake_summary.server_public_key.key;
const handshake_commitment = tlsnProof.session.header.handshake_summary.handshake_commitment;

const sessionHeader = new SessionHeader({
encoderSeed: bytesToFields(new Uint8Array(encoder_seed)),
merkleRoot: bytesToFields(new Uint8Array(merkle_root)),
sentLen: bytesToFields(new Uint8Array(sent_len)),
recvLen: bytesToFields(new Uint8Array(recv_len)),
handshakeSummary: {
time: bytesToFields(new Uint8Array(time)),
serverPublicKey: {
group: bytesToFields(new Uint8Array(group)),
key: bytesToFields(new Uint8Array(key)),
},
handshakeCommitment: bytesToFields(new Uint8Array(handshake_commitment)),
},
});

// ----------------------------------------------------
// Create a local blockchain instance
const Local = Mina.LocalBlockchain({ proofsEnabled: false });
Mina.setActiveInstance(Local);
const { privateKey: deployerKey, publicKey: deployerAccount } =
Local.testAccounts[0];
const { privateKey: senderKey, publicKey: senderAccount } =
Local.testAccounts[1];
// ----------------------------------------------------

// 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 Square - and deploy it to zkAppAddress
// create an instance of TlsnVerifier - and deploy it to zkAppAddress
const zkAppInstance = new TlsnVerifier(zkAppAddress);
const deployTxn = await Mina.transaction(deployerAccount, () => {
AccountUpdate.fundNewAccount(deployerAccount);
zkAppInstance.deploy();
zkAppInstance.notaryPublicKey.set(PublicKey.fromBase58("B62qowWuY2PsBZsm64j4Uu2AB3y4L6BbHSvtJcSLcsVRXdiuycbi8Ws"));
});
await deployTxn.sign([deployerKey, zkAppPrivateKey]).send();
// get the initial state of Square after deployment
const notaryPublicKey = zkAppInstance.notaryPublicKey.get();
console.log('state after init:', notaryPublicKey.toString());

// ----------------------------------------------------
// Serialize the header object in a deterministic manner

// const Header = bcs.struct('Header', {
// encoder_seed: bcs.fixedArray(32, bcs.u8()),
// merkle_root: bcs.fixedArray(32, bcs.u8()),
// sent_len: bcs.u64(),
// recv_len: bcs.u64(),
// handshake_summary: bcs.struct('HandshakeSummary', {
// time: bcs.u64(),
// server_public_key: bcs.struct('ServerPublicKey', {
// group: bcs.string(),
// key: bcs.fixedArray(32, bcs.u8()),
// }),
// handshake_commitment: bcs.fixedArray(32, bcs.u8()),
// }),
// });

// const header = Header.serialize(tlsnProof.session.header);
// const headerBytes = header.toBytes();

// const headerFields: Field[] = [];

// headerBytes.forEach((byte: number) => headerFields.push(Field(byte)));

// const signatureBytes = tlsnProof.session.signature.map((byte: number) =>
// Field(byte)
// );

// const signature = Signature.fromFields(signatureBytes);

// const txn1 = await Mina.transaction(senderAccount, () => {
// zkAppInstance.verify(headerFields, signature);
// });
// await txn1.prove();
const txn1 = await Mina.transaction(senderAccount, () => {
zkAppInstance.verify(sessionHeader, signature);
});
await txn1.prove();
await txn1.sign([senderKey]).send();

// await txn1.sign([senderKey]).send();
} else {
// Data is invalid
console.error('Invalid data:', result.error);
Expand Down
Loading

0 comments on commit 887ea5b

Please sign in to comment.