From 653775462cec3bac91a8c419262934346a54dcc7 Mon Sep 17 00:00:00 2001 From: Kai Hirota <34954529+kaihirota@users.noreply.github.com> Date: Mon, 19 Feb 2024 16:04:21 +1100 Subject: [PATCH] add registration workflow --- src/ImmutableX.ts | 12 +++++++ src/types/signers.ts | 11 +++++++ src/utils/stark/starkSigner.ts | 26 +++++++++++++++ src/workflows/registration.ts | 58 ++++++++++++++++++++++++++++++++-- src/workflows/workflows.ts | 7 ++++ 5 files changed, 111 insertions(+), 3 deletions(-) diff --git a/src/ImmutableX.ts b/src/ImmutableX.ts index 560c68ba..14e7368b 100644 --- a/src/ImmutableX.ts +++ b/src/ImmutableX.ts @@ -169,6 +169,18 @@ export class ImmutableX { }); } + /** + * Register a User to StarkEx contract if they are not already + * @param walletConnection - the pair of L1/L2 signers + * @returns a promise that resolves with void if successful + * @throws {@link index.IMXError} + */ + public registerOnchain(walletConnection: WalletConnection) { + return this.workflows.registerOnchain(walletConnection).catch(err => { + throw formatError(err); + }); + } + /** * Checks if a User is registered on on-chain * @param walletConnection - the pair of L1/L2 signers diff --git a/src/types/signers.ts b/src/types/signers.ts index 706364a3..44ccccdd 100644 --- a/src/types/signers.ts +++ b/src/types/signers.ts @@ -1,4 +1,5 @@ import { Signer as EthSigner } from '@ethersproject/abstract-signer'; +import { ec } from 'elliptic'; export { EthSigner }; @@ -13,6 +14,16 @@ export interface StarkSigner { * @returns the signed prefixed-message */ signMessage(message: string): Promise; + + /** + * Signs the prefixed-message + * @params message - this must be a UTF8-message + * @returns the signed prefixed-message + */ + sign(message: string): Promise; + + getYCoordinate(): string; + /** * Get the Signer address * @returns the Signer's checksum address diff --git a/src/utils/stark/starkSigner.ts b/src/utils/stark/starkSigner.ts index 79f5afef..68fde42b 100644 --- a/src/utils/stark/starkSigner.ts +++ b/src/utils/stark/starkSigner.ts @@ -28,6 +28,17 @@ export class StandardStarkSigner implements StarkSigner { ); } + public async sign(msg: string): Promise { + return this.keyPair.sign(this.fixMsgHashLen(msg)); + } + + public getYCoordinate(): string { + return encUtils.sanitizeBytes( + this.keyPair.getPublic().getY().toString(16), + 2, + ); + } + /* The function _truncateToN in lib/elliptic/ec/index.js does a shift-right of delta bits, if delta is positive, where @@ -60,3 +71,18 @@ export class StandardStarkSigner implements StarkSigner { export function createStarkSigner(starkPrivateKey: string): StarkSigner { return new StandardStarkSigner(starkPrivateKey); } + +export function serializePackedSignature( + sig: ec.Signature, + pubY: string, +): string { + return encUtils.sanitizeHex( + encUtils.padLeft(sig.r.toString(16), 64) + + encUtils.padLeft(sig.s.toString(16), 64, '0') + + encUtils.padLeft( + new BN(encUtils.removeHexPrefix(pubY), 'hex').toString(16), + 64, + '0', + ), + ); +} diff --git a/src/workflows/registration.ts b/src/workflows/registration.ts index 30dc41a8..e888e1d4 100644 --- a/src/workflows/registration.ts +++ b/src/workflows/registration.ts @@ -1,11 +1,17 @@ +import { ImmutableXConfiguration } from '..'; import { UsersApi, GetSignableRegistrationResponse, RegisterUserResponse, } from '../api'; -import { WalletConnection } from '../types'; -import { signRaw } from '../utils'; -import { Registration } from '../contracts'; +import { StarkSigner, WalletConnection } from '../types'; +import { serializePackedSignature, signRaw, starkEcOrder } from '../utils'; +import { Registration, StarkV4__factory } from '../contracts'; +import { solidityKeccak256 } from 'ethers/lib/utils'; +import BN from 'bn.js'; +import * as encUtils from 'enc-utils'; +import { ec } from 'elliptic'; +import { TransactionResponse } from '@ethersproject/providers'; type registerOffchainWorkflowParams = WalletConnection & { usersApi: UsersApi; @@ -81,3 +87,49 @@ export async function getSignableRegistrationOnchain( verification_signature: response.data.verification_signature, }; } + +export async function signRegisterEthAddress( + starkSigner: StarkSigner, + ethAddress: string, + starkPublicKey: string, +): Promise { + const hash: string = solidityKeccak256( + ['string', 'address', 'uint256'], + ['UserRegistration:', ethAddress, starkPublicKey], + ); + const msgHash: BN = new BN(encUtils.removeHexPrefix(hash), 16); + const modMsgHash: BN = msgHash.mod(starkEcOrder); + const signature: ec.Signature = await starkSigner.sign( + modMsgHash.toString(16), + ); + const pubY: string = encUtils.sanitizeHex(starkSigner.getYCoordinate()); + return serializePackedSignature(signature, pubY); +} + +export async function registerOnchainWorkflow( + walletConnection: WalletConnection, + config: ImmutableXConfiguration, +): Promise { + const ethAddress = await walletConnection.ethSigner.getAddress(); + const starkPublicKey = await walletConnection.starkSigner.getAddress(); + + const signature = await signRegisterEthAddress( + walletConnection.starkSigner, + ethAddress, + starkPublicKey, + ); + + const contract = StarkV4__factory.connect( + config.ethConfiguration.coreContractAddress, + walletConnection.ethSigner, + ); + + const populatedTransaction = + await contract.populateTransaction.registerEthAddress( + ethAddress, + starkPublicKey, + signature, + ); + + return walletConnection.ethSigner.sendTransaction(populatedTransaction); +} diff --git a/src/workflows/workflows.ts b/src/workflows/workflows.ts index 8b645c37..cc4021d4 100644 --- a/src/workflows/workflows.ts +++ b/src/workflows/workflows.ts @@ -42,6 +42,7 @@ import { Registration__factory } from '../contracts'; import { isRegisteredOnChainWorkflow, registerOffchainWorkflow, + registerOnchainWorkflow, } from './registration'; import { mintingWorkflow } from './minting'; import { transfersWorkflow, batchTransfersWorkflow } from './transfers'; @@ -131,6 +132,12 @@ export class Workflows { }); } + public async registerOnchain(walletConnection: WalletConnection) { + await this.validateChain(walletConnection.ethSigner); + + return registerOnchainWorkflow(walletConnection, this.config); + } + public async isRegisteredOnchain(walletConnection: WalletConnection) { await this.validateChain(walletConnection.ethSigner);