Skip to content

Commit

Permalink
All the interface updates
Browse files Browse the repository at this point in the history
  • Loading branch information
bh2smith committed Jul 23, 2024
1 parent 678eae2 commit a3c90d1
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 60 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ await adapter.signAndSendTransaction({
receiver: "0xdeADBeeF0000000000000000000000000b00B1e5",
amount: 1n,
chainId: 11_155_111,
// Optional: Set nearGas (default is 300 TGAS, which sometimes might not be sufficient)
// Optional: Set nearGas (default is 250 TGAS, which sometimes might not be sufficient)
});
```

Expand Down
34 changes: 21 additions & 13 deletions src/chains/ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class NearEthAdapter {
* acquires signature from Near MPC Contract and submits transaction to public mempool.
*
* @param {BaseTx} txData - Minimal transaction data to be signed by Near MPC and executed on EVM.
* @param {bigint} nearGas - manually specified gas to be sent with signature request (default 300 TGAS).
* @param {bigint} nearGas - manually specified gas to be sent with signature request.
* Note that the signature request is a recursive function.
*/
async signAndSendTransaction(
Expand All @@ -98,12 +98,12 @@ export class NearEthAdapter {
console.log("Creating Payload for sender:", this.address);
const { transaction, signArgs } = await this.createTxPayload(txData);
console.log("Requesting signature from Near...");
const { big_r, big_s } = await this.mpcContract.requestSignature(
const signature = await this.mpcContract.requestSignature(
signArgs,
nearGas
);
console.log("Signature received");
return this.relayTransaction({ transaction, signature: { big_r, big_s } });
console.log("Raw signature received");
return this.relayTransaction({ transaction, signature });
}

/**
Expand All @@ -112,19 +112,18 @@ export class NearEthAdapter {
* acquires signature from Near MPC Contract and submits transaction to public mempool.
*
* @param {BaseTx} txData - Minimal transaction data to be signed by Near MPC and executed on EVM.
* @param {bigint} nearGas - manually specified gas to be sent with signature request (default 300 TGAS).
* @param {bigint} nearGas - manually specified gas to be sent with signature request.
* Note that the signature request is a recursive function.
*/
async getSignatureRequestPayload(
txData: BaseTx,
nearGas?: bigint
): Promise<{
transaction: Hex;
requestPayload: FunctionCallTransaction<SignArgs>;
requestPayload: FunctionCallTransaction<{ request: SignArgs }>;
}> {
console.log("Creating Payload for sender:", this.address);
const { transaction, signArgs } = await this.createTxPayload(txData);
console.log("Requesting signature from Near...");
return {
transaction,
requestPayload: this.mpcContract.encodeSignatureRequestTx(
Expand All @@ -143,7 +142,7 @@ export class NearEthAdapter {
mpcSignRequest(
transaction: Hex,
nearGas?: bigint
): FunctionCallTransaction<SignArgs> {
): FunctionCallTransaction<{ request: SignArgs }> {
return this.mpcContract.encodeSignatureRequestTx(
{
payload: buildTxPayload(transaction),
Expand Down Expand Up @@ -282,7 +281,7 @@ export class NearEthAdapter {

const { big_r, big_s } = await this.mpcContract.requestSignature({
path: this.derivationPath,
payload: Array.from(hashToSign.reverse()),
payload: Array.from(hashToSign),
key_version: 0,
});
const r = `0x${big_r.substring(2)}` as Hex;
Expand All @@ -298,17 +297,26 @@ export class NearEthAdapter {
recoveryData: RecoveryData,
signatureData: MPCSignature
): Promise<Hex> {
const { big_r, big_s } = signatureData;
const {
big_r: { affine_point },
s: { scalar },
recovery_id,
} = signatureData;
const fixedSig = {
big_r: affine_point,
big_s: scalar,
yParity: recovery_id,
};
if (recoveryData.type === "eth_sendTransaction") {
const signature = addSignature(
{ transaction: recoveryData.data as Hex, signature: signatureData },
{ transaction: recoveryData.data as Hex, signature: fixedSig },
this.address
);
// Returns relayed transaction hash (without waiting for confirmation).
return this.relaySignedTransaction(signature, false);
}
const r = `0x${big_r.substring(2)}` as Hex;
const s = `0x${big_s}` as Hex;
const r = `0x${affine_point.substring(2)}` as Hex;
const s = `0x${scalar}` as Hex;
const sigs: [Hex, Hex] = [
serializeSignature({ r, s, yParity: 0 }),
serializeSignature({ r, s, yParity: 1 }),
Expand Down
1 change: 1 addition & 0 deletions src/chains/near.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { keyStores, KeyPair, connect, Account } from "near-api-js";

export const TGAS = 1000000000000n;
export const NO_DEPOSIT = "0";
export const ONE_YOCTO = "1";

export interface NearConfig {
networkId: string;
Expand Down
37 changes: 22 additions & 15 deletions src/mpcContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ import {
najPublicKeyStrToUncompressedHexPoint,
uncompressedHexPointToEvmAddress,
} from "./utils/kdf";
import { NO_DEPOSIT, TGAS } from "./chains/near";
import { MPCSignature, FunctionCallTransaction, SignArgs } from "./types/types";
import { TGAS, ONE_YOCTO } from "./chains/near";
import {
MPCSignature,
FunctionCallTransaction,
SignArgs,
ReducedSignature,
} from "./types/types";

const DEFAULT_MPC_CONTRACT = "v2.multichain-mpc.testnet";

Expand All @@ -16,18 +21,20 @@ export interface ChangeMethodArgs<T> {
args: T;
/// GasLimit on transaction execution.
gas: string;
/// Deposit (i.e. payable amount) to attach to transaction.
attachedDeposit: string;
/// Account Signing the call
signerAccount: Account;
/// attachedDeposit (i.e. payable amount) to attach to transaction.
amount: string;
}

interface MultichainContractInterface extends Contract {
// Define the signature for the `public_key` view method
public_key: () => Promise<string>;

// Define the signature for the `sign` change method
sign: (args: ChangeMethodArgs<SignArgs>) => Promise<[string, string]>;
sign: (
args: ChangeMethodArgs<{ request: SignArgs }>
) => Promise<MPCSignature>;
}

/**
Expand Down Expand Up @@ -63,20 +70,20 @@ export class MultichainContract {
requestSignature = async (
signArgs: SignArgs,
gas?: bigint
): Promise<MPCSignature> => {
const [big_r, big_s] = await this.contract.sign({
args: signArgs,
): Promise<ReducedSignature> => {
const { big_r, s, recovery_id } = await this.contract.sign({
signerAccount: this.connectedAccount,
args: { request: signArgs },
gas: gasOrDefault(gas),
attachedDeposit: NO_DEPOSIT,
amount: ONE_YOCTO,
});
return { big_r, big_s };
return { big_r: big_r.affine_point, big_s: s.scalar, yParity: recovery_id };
};

encodeSignatureRequestTx(
signArgs: SignArgs,
gas?: bigint
): FunctionCallTransaction<SignArgs> {
): FunctionCallTransaction<{ request: SignArgs }> {
return {
signerId: this.connectedAccount.accountId,
receiverId: this.contract.contractId,
Expand All @@ -85,9 +92,9 @@ export class MultichainContract {
type: "FunctionCall",
params: {
methodName: "sign",
args: signArgs,
args: { request: signArgs },
gas: gasOrDefault(gas),
deposit: NO_DEPOSIT,
deposit: ONE_YOCTO,
},
},
],
Expand All @@ -99,6 +106,6 @@ function gasOrDefault(gas?: bigint): string {
if (gas !== undefined) {
return gas.toString();
}
// Default of 300 TGAS
return (TGAS * 300n).toString();
// Default of 250 TGAS
return (TGAS * 250n).toString();
}
20 changes: 18 additions & 2 deletions src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export interface NearEthAdapterParams {

export interface NearEthTxData {
evmMessage: string | TransactionSerializable;
nearPayload: FunctionCallTransaction<SignArgs>;
nearPayload: FunctionCallTransaction<{ request: SignArgs }>;
recoveryData: RecoveryData;
}

Expand Down Expand Up @@ -80,10 +80,26 @@ export interface FunctionCallTransaction<T> {
/**
* Result Type of MPC contract signature request.
* Representing Affine Points on eliptic curve.
* Example: {
"big_r": {
"affine_point": "031F2CE94AF69DF45EC96D146DB2F6D35B8743FA2E21D2450070C5C339A4CD418B"
},
"s": { "scalar": "5AE93A7C4138972B3FE8AEA1638190905C6DB5437BDE7274BEBFA41DDAF7E4F6"
},
"recovery_id": 0
}
*/

export interface MPCSignature {
big_r: { affine_point: string };
s: { scalar: string };
recovery_id: number;
}

export interface ReducedSignature {
big_r: string;
big_s: string;
yParity: number;
}

export interface MessageData {
Expand Down Expand Up @@ -114,5 +130,5 @@ export interface TransactionWithSignature {
/// Unsigned Ethereum transaction data.
transaction: Hex;
/// Representation of the transaction's signature.
signature: MPCSignature;
signature: ReducedSignature;
}
4 changes: 2 additions & 2 deletions src/utils/signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ export async function signatureFromTxHash(
}
if (base64Sig) {
const decodedValue = Buffer.from(base64Sig, "base64").toString("utf-8");
const [big_r, big_s] = JSON.parse(decodedValue);
return { big_r, big_s };
const [big_r, s, recovery_id] = JSON.parse(decodedValue);
return { big_r, s, recovery_id };
} else {
throw new Error("No valid values found in the array.");
}
Expand Down
47 changes: 20 additions & 27 deletions src/utils/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function toPayload(hexString: Hex): number[] {
if (hexString.slice(2).length !== 32 * 2) {
throw new Error(`Payload Hex must have 32 bytes: ${hexString}`);
}
return Array.from(toBytes(hexString).reverse());
return Array.from(toBytes(hexString));
}

export function buildTxPayload(unsignedTxHash: `0x${string}`): number[] {
Expand Down Expand Up @@ -57,36 +57,29 @@ export async function populateTx(
}

export function addSignature(
{ transaction, signature: { big_r, big_s } }: TransactionWithSignature,
{
transaction,
signature: { big_r, big_s, yParity },
}: TransactionWithSignature,
sender: Address
): Hex {
const txData = parseTransaction(transaction);
const r = `0x${big_r.substring(2)}` as Hex;
const s = `0x${big_s}` as Hex;

const candidates = [27n, 28n].map((v) => {
return {
v,
r,
s,
...txData,
};
});
const signature = {
r: `0x${big_r.substring(2)}` as Hex,
s: `0x${big_s}` as Hex,
yParity,
};
const signedTx = {
...signature,
...txData,
};
const pk = publicKeyToAddress(
recoverPublicKey(keccak256(transaction), serializeSignature(signature))
);

const signedTx = candidates.find((tx) => {
const signature = serializeSignature({
r: tx.r!,
s: tx.s!,
v: tx.v!,
});
const pk = publicKeyToAddress(
recoverPublicKey(keccak256(transaction), signature)
);
return pk.toLowerCase() === sender.toLowerCase();
});
if (!signedTx) {
throw new Error("Signature is not valid");
}
console.log("Public Key", pk);
console.log("Incorrect Sender", sender);
console.log(signedTx);
return serializeTransaction(signedTx);
}

Expand Down

0 comments on commit a3c90d1

Please sign in to comment.