Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mina-contracts package #389

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions package-lock.json

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

3 changes: 3 additions & 0 deletions packages/contracts/mina-delegation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Paima Mina contracts

NPM package for Mina programs and contracts for Paima Engine and related utilities.
37 changes: 37 additions & 0 deletions packages/contracts/mina-delegation/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@paima/mina-delegation",
"version": "3.1.0",
"description": "Mina ZkProgram for EVM->Mina delegation",
"author": "Paima Studios",
"license": "MIT",
"type": "module",
"files": [
"build"
],
"main": "./build/index.js",
"types": "./build/index.d.ts",
"exports": {
".": {
"default": "./build/index.js",
"types": "./build/index.d.ts"
}
},
"scripts": {
"build": "tsc",
"clean": "rm -r tsconfig.tsbuildinfo build/",
"prepare": "npm run build",
"prepack": "npm run clean"
},
"keywords": [
"mina",
"zk",
"o1js"
],
"dependencies": {
"o1js": "^1.3.1"
},
"devDependencies": {
"eslint-plugin-o1js": "^0.4.0",
"typescript": "^5.3.3"
}
}
123 changes: 123 additions & 0 deletions packages/contracts/mina-delegation/src/delegate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import {
Bool,
Bytes,
Crypto,
Poseidon,
PublicKey,
Struct,
UInt8,
ZkProgram,
createEcdsa,
createForeignCurve,
} from 'o1js';

// ----------------------------------------------------------------------------
// Common data types

/** A Mina foreign curve for Secp256k1, like Ethereum uses. */
export class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {
/** Convert a standard 0x04{128 hex digits} public key into this provable struct. */
static fromHex(publicKey: `0x${string}`): Secp256k1 {
if (!publicKey.startsWith('0x04') || publicKey.length != 4 + 64 + 64) {
throw new Error('Bad public key format');
}
return Secp256k1.from({
x: BigInt('0x' + publicKey.substring(4, 4 + 64)),
y: BigInt('0x' + publicKey.substring(4 + 64, 4 + 64 + 64)),
});
}
}

/** A Mina-provable ECDSA signature on the Secp256k1 curve, like Ethereum uses. */
export class Ecdsa extends createEcdsa(Secp256k1) {
// o1js-provided fromHex is good enough
}

// Ethereum's fixed prefix.
const ethereumPrefix = Bytes.fromString('\x19Ethereum Signed Message:\n');
// A prefix to distinguish this delegation order scheme from what might be
// similar-looking messages.
const delegationPrefix = Bytes.fromString('DELEGATE-WALLET:');

/**
* An order that a particular EVM address has signed to authorize (delegate)
* a Mina address to act on its behalf.
*/
export class DelegationOrder extends Struct({
/** Mina public key that the delegation order is issued for. */
target: PublicKey,
/** Ethereum public key that signed the delegation order. */
signer: Secp256k1.provable,
}) {
private _innerMessage(): Bytes {
return Bytes.from([...delegationPrefix.bytes, ...encodeKey(this.target)]);
}

/** Get the message for an Etherum wallet to sign, WITHOUT the Ethereum prefix. */
bytesToSign(): Uint8Array {
return this._innerMessage().toBytes();
}

/** Validate that the given Ethereum signature matches this order, WITH the Ethereum prefix. */
assertSignatureMatches(signature: Ecdsa) {
const inner = this._innerMessage();
const fullMessage = Bytes.from([
...ethereumPrefix.bytes,
...Bytes.fromString(String(inner.length)).bytes,
...inner.bytes,
]);
signature.verifyV2(fullMessage, this.signer).assertTrue();
}

/** Hash this entire order for use as a MerkleMap key. */
hash() {
return Poseidon.hashWithPrefix('DelegationOrder', [
...this.target.toFields(),
...this.signer.x.toFields(),
...this.signer.y.toFields(),
]);
}
}

function encodeKey(k: PublicKey): UInt8[] {
const bytes = [];
const bits = [...k.x.toBits(254), k.isOdd];
for (let i = 0; i < bits.length; i += 8) {
let value = new UInt8(0);
for (let j = 0; j < 8; j++) {
value = value.mul(2).add(boolToU8(bits[i + j] ?? Bool(false)));
}
bytes.push(value);
}
return bytes;
}

function boolToU8(bool: Bool): UInt8 {
return UInt8.from(bool.toField());
}

// ----------------------------------------------------------------------------
// The provable program itself

/**
* A simple {@link ZkProgram} that proves that a valid signature exists for an
* input {@link DelegationOrder}.
*/
export const DelegationOrderProgram = ZkProgram({
name: 'DelegationOrderProgram',

publicInput: DelegationOrder,

methods: {
sign: {
privateInputs: [Ecdsa.provable],

async method(order: DelegationOrder, signature: Ecdsa) {
order.assertSignatureMatches(signature);
},
},
},
});

/** A verifiable proof of {@link DelegationOrderProgram}'s success. */
export class DelegationOrderProof extends ZkProgram.Proof(DelegationOrderProgram) {}
7 changes: 7 additions & 0 deletions packages/contracts/mina-delegation/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export {
DelegationOrder,
DelegationOrderProgram,
DelegationOrderProof,
Ecdsa,
Secp256k1,
} from './delegate.js';
12 changes: 12 additions & 0 deletions packages/contracts/mina-delegation/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "build",

// TS decorator metadata is necessary for SmartContracts to compile.
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
},
"include": ["./src"],
}
1 change: 1 addition & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@paima/db/*": ["./packages/node-sdk/paima-db/*"],
"@paima/aiken-mdx/*": ["./packages/cardano-contracts/aiken-mdx/*"],
"@paima/evm-contracts/*": ["./packages/contracts/evm-contracts/*"],
"@paima/mina-delegation/*": ["./packages/contracts/mina-delegation/*"],
"@paima/executors/*": ["./packages/paima-sdk/paima-executors/*"],
"@paima/funnel/*": ["./packages/engine/paima-funnel/*"],
"@paima/mw-core/*": ["./packages/paima-sdk/paima-mw-core/*"],
Expand Down