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 middleware helpers for Mina #393

Draft
wants to merge 35 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
1177469
Add Mina contracts for EVM->Mina DelegationOrder proofs
SpaceManiac Jun 24, 2024
5dfef13
Add first PrebuiltCache version to improve load times
SpaceManiac Jun 24, 2024
c320e60
Add stub test.ts
SpaceManiac Jun 25, 2024
9ea81b2
Add test that verifies prebuilt cache works
SpaceManiac Jun 25, 2024
c5b365e
Prebuilt: support SmartContract, test better, clean up
SpaceManiac Jun 26, 2024
6c2d729
Restore original shorter delegation prefix
SpaceManiac Jun 26, 2024
35de307
Skip removal and run twice because apparently you have to do that
SpaceManiac Jun 26, 2024
62759d3
Harmonize signed message prefix with rest of Paima
SpaceManiac Jun 28, 2024
d9b6816
Rename package to mina-delegation
SpaceManiac Jun 28, 2024
231c557
Trash cache, since 700M is unreasonable to put on NPM
SpaceManiac Jul 3, 2024
e153e16
Add 'main' and 'types' so old import schemes find us
SpaceManiac Jul 9, 2024
f17243f
Fix path in tsconfig.base.json
SpaceManiac Jul 9, 2024
53e6f97
Merge branch 'master' into patch/mina-contracts-1
SpaceManiac Jul 15, 2024
d312170
Let IProvider.signMessage accept Uint8Array
SpaceManiac Jun 27, 2024
29c35ff
Make DelegationOrder.bytesToSign static so it can be used w/o an Eth key
SpaceManiac Jul 9, 2024
25517af
Update to o1js 1.4.0
SpaceManiac Jul 9, 2024
ce3ef33
Accept bare 0x public keys in Secp256k1.fromHex
SpaceManiac Jul 9, 2024
bd77e20
Re-export o1js PublicKey to solve instanceof woes
SpaceManiac Jul 9, 2024
426c615
Use o1js's v2 foreignCurve and ecdsa support
SpaceManiac Jul 15, 2024
ff52eb2
Revert "Use o1js's v2 foreignCurve and ecdsa support"
SpaceManiac Jul 15, 2024
98e20f8
Add @aiken-lang/aiken as a dev dependency
SpaceManiac Jul 16, 2024
07ba92a
Downgrade o1js to 1.5.0 in lockfile
SpaceManiac Jul 16, 2024
bdaa213
Use base64 in DelegationOrder to avoid binary mess in wallets
SpaceManiac Jul 16, 2024
3f898f6
Tweak bytesToSign API
SpaceManiac Jul 17, 2024
ba90ddf
Proactively fix up PublicKeys with wrong instanceof
SpaceManiac Jul 17, 2024
9226f89
Delete unused DelegationOrder.hash function
SpaceManiac Jul 19, 2024
fa53b58
Make DelegationOrder prefix configurable
SpaceManiac Jul 19, 2024
bc3ec8d
Merge delegate.ts into index.ts
SpaceManiac Jul 19, 2024
5aa0f8c
Add DelegationOrderProof type
SpaceManiac Jul 25, 2024
6715d77
Simplify calling Secp256k1.fromHex by accepting string
SpaceManiac Jul 25, 2024
50affe7
s/DelegationOrder/DelegationCommand/
SpaceManiac Jul 26, 2024
5d2060b
Add MinaDelegationCache class and worker scaffolding to support it
SpaceManiac Jul 30, 2024
1cf642b
Revert IProvider.signMessage to require string, but impls still suppo…
SpaceManiac Jul 30, 2024
f1b7515
Merge branch 'master' into patch/mina-middleware
SpaceManiac Jul 30, 2024
0dad8cf
Use o1js 1.6.0+ for V2 foreign curves
SpaceManiac Jul 30, 2024
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
74 changes: 74 additions & 0 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion packages/batcher/batcher-transaction-poster/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"references": [
{ "path": "../utils" },
{ "path": "../db" },
{ "path": "../../paima-sdk/paima-providers/tsconfig.build.json" },
{ "path": "../../paima-sdk/paima-providers" },
{ "path": "../../paima-sdk/paima-concise/tsconfig.build.json" },
{ "path": "../../paima-sdk/paima-utils/tsconfig.build.json" },
{ "path": "../../paima-sdk/paima-events/tsconfig.build.json" },
Expand Down
2 changes: 1 addition & 1 deletion packages/batcher/runtime/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
{ "path": "../game-input-validator/" },
{ "path": "../utils" },
{ "path": "../batcher-transaction-poster" },
{ "path": "../../paima-sdk/paima-providers/tsconfig.build.json" },
{ "path": "../../paima-sdk/paima-providers" },
{ "path": "../../paima-sdk/paima-utils/tsconfig.build.json" },
{ "path": "../../node-sdk/paima-utils-backend" },
{ "path": "../../paima-sdk/paima-mw-core/tsconfig.json" },
Expand Down
2 changes: 1 addition & 1 deletion packages/batcher/utils/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"include": ["src/**/*"],
"references": [
{ "path": "../../paima-sdk/paima-utils/tsconfig.build.json" },
{ "path": "../../paima-sdk/paima-providers/tsconfig.build.json" },
{ "path": "../../paima-sdk/paima-providers" },
{ "path": "../../paima-sdk/paima-mw-core/tsconfig.json" },
]
}
2 changes: 1 addition & 1 deletion packages/batcher/webserver/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{ "path": "../address-validator/" },
{ "path": "../db/" },
{ "path": "../utils" },
{ "path": "../../paima-sdk/paima-providers/tsconfig.build.json" },
{ "path": "../../paima-sdk/paima-providers" },
{ "path": "../../paima-sdk/paima-concise/tsconfig.build.json" },
{ "path": "../../paima-sdk/paima-utils/tsconfig.build.json" },
]
Expand Down
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.6.0"
},
"devDependencies": {
"eslint-plugin-o1js": "^0.4.0",
"typescript": "^5.3.3"
}
}
186 changes: 186 additions & 0 deletions packages/contracts/mina-delegation/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import {
Bool,
Bytes,
Crypto,
DynamicProof,
Proof,
PublicKey,
Struct,
UInt8,
Void,
ZkProgram,
createEcdsaV2,
createForeignCurveV2,
} from 'o1js';

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

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

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

/** Ethereum's fixed prefix for `personal_sign` messages. **/
const ethereumPrefix = Bytes.fromString('\x19Ethereum Signed Message:\n');

/** Pack 254 bits of key's X and 1 bit of isOdd into 32 bytes. */
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());
}

export type DelegationCommandProof = Proof<
{
// real data
target: PublicKey;
signer: Secp256k1;
// sort of a type system marker that the interior is a DelegationCommand?
assertSignatureMatches(signature: Ecdsa): void;
},
void
>;
type _DelegationCommandProof = DelegationCommandProof;

/**
* Prepare a ZkProgram that verifies an EVM signature delegating to a Mina
* address under a specific prefix. The prefix will be seen by the user when
* signing the message and should clearly indicate the limited scope of the
* validity of the signature.
*
* @param prefix A prefix to distinguish these delegation orders from those for other systems.
* @returns An o1js Struct, ZkProgram, and Proof tied to that prefix.
*
* @example
* const { DelegationCommand, DelegationCommandProgram, DelegationCommandProof } =
* delegateEvmToMina('Click & Moo login: ');
*/
export function delegateEvmToMina(prefix: string) {
// ----------------------------------------------------------------------------
// Per-prefix data types
const delegationPrefix = Bytes.fromString(prefix);

class DelegationCommand 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,
}) {
constructor(value: { target: PublicKey; signer: Secp256k1 }) {
if (!(value.target instanceof PublicKey)) {
// Compensate for the possibility of duplicate o1js libraries that aren't
// `instanceof` each other, which messes up checks inside o1js. Can be
// caused by `npm link`ing to this package, for example.
value = { ...value, target: PublicKey.fromBase58((value.target as PublicKey).toBase58()) };
}
super(value);
}

static #_innerMessage(target: PublicKey): Bytes {
return Bytes.from([
...delegationPrefix.bytes,
// Base64-encode encodeKey()
...Bytes.from(encodeKey(target)).base64Encode().bytes,
]);
}

/**
* Get the message for an Etherum wallet to sign, WITHOUT the Ethereum prefix.
* This is printable and should be passed to something like `personal_sign`.
*/
static bytesToSign({ target }: { target: PublicKey }): Uint8Array {
// Accepts an object so you can pass just a PublicKey OR a DelegationCommand.
return this.#_innerMessage(target).toBytes();
}

/** Validate that the given Ethereum signature matches this order, WITH the Ethereum prefix. */
assertSignatureMatches(signature: Ecdsa) {
const inner = DelegationCommand.#_innerMessage(this.target);
const fullMessage = Bytes.from([
...ethereumPrefix.bytes,
// NOTE: `inner.length` is effectively a constant so it's okay to bake it in.
...Bytes.fromString(String(inner.length)).bytes,
...inner.bytes,
]);
signature.verifyV2(fullMessage, this.signer).assertTrue();
}
}

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

const DelegationCommandProgram = ZkProgram({
name: `${prefix}DelegationCommandProgram`,

publicInput: DelegationCommand,

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

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

class DelegationCommandProof
extends ZkProgram.Proof(DelegationCommandProgram)
implements _DelegationCommandProof {}

class DynamicDelegationCommandProof extends DynamicProof<typeof DelegationCommand, void> {
static override publicInputType = DelegationCommand;
static override publicOutputType = Void;
static override maxProofsVerified = 0 as const;
}

return {
/**
* An order that a particular EVM address has signed to authorize (delegate)
* a Mina address to act on its behalf.
*/
DelegationCommand,
/**
* A simple {@link ZkProgram} that proves that a valid signature exists for an
* input {@link DelegationCommand}.
*/
DelegationCommandProgram,
/** A verifiable proof of {@link DelegationCommandProgram}'s success. */
DelegationCommandProof,
// /** A dynamic version of {@link DelegationCommandProof}. */
// DynamicDelegationCommandProof, <- causes ts4904 until we explicitly declare the return type
};
}
Loading