Skip to content
This repository has been archived by the owner on Apr 8, 2024. It is now read-only.

[PI-139]: Generate stark signature #550

Merged
merged 7 commits into from
Aug 7, 2023
Merged
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
2 changes: 1 addition & 1 deletion env/test
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Add Web3 url
RPC_URL=https://ropsten.infura.io/v3/7bfab7398ae84af3b1b70c955cfd9491
RPC_URL=https://goerli.infura.io/v3/3d572ae9b01e45c789c3214696bb02d4
# Add GAS STATION API KEY
# Ethereum private key prefixed with 0x
PRIVATE_ETH_KEY=0x49e4d1e2aa7d026188251392dd2d335c176d846d8a894a8092c835f3b345e2ad
Expand Down
65 changes: 65 additions & 0 deletions examples/33.generateL1RegistrationPayload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env -S yarn node
/* eslint-disable no-unused-vars */

/*
DO NOT EDIT THIS FILE BY HAND!
Examples are generated using helpers/buildExamples.js script.
Check README.md for more details.
*/

const sw = require('@rhino.fi/starkware-crypto')
const getWeb3 = require('./helpers/getWeb3')

const RhinofiClientFactory = require('../src')
const envVars = require('./helpers/loadFromEnvOrConfig')(
process.env.CONFIG_FILE_NAME
)
const logExampleResult = require('./helpers/logExampleResult')(__filename)

const ethPrivKey = envVars.ETH_PRIVATE_KEY
// NOTE: you can also generate a new key using:`
// const starkPrivKey = rhinofi.stark.createPrivateKey()
const starkPrivKey = envVars.STARK_PRIVATE_KEY
const rpcUrl = envVars.RPC_URL

const { web3, provider } = getWeb3(ethPrivKey, rpcUrl)

const rhinofiConfig = {
api: envVars.API_URL,
dataApi: envVars.DATA_API_URL,
useAuthHeader: true,
wallet: {
type: 'tradingKey',
meta: {
starkPrivateKey: starkPrivKey
}
}
// Add more variables to override default values
}

;(async () => {
const rhinofi = await RhinofiClientFactory(web3, rhinofiConfig)

const { starkKeyHex, ethAddress } = await rhinofi.getUserConfig()

const l1RegistrationSignature = await rhinofi.stark.signRegistration(
ethAddress
)

const callData = await rhinofi.stark.l1RegistrationCallData(
starkKeyHex,
ethAddress,
l1RegistrationSignature
)

logExampleResult({
ethAddress,
starkKeyHex,
sig: l1RegistrationSignature,
callData
})
})()
.catch(error => {
console.error(error)
process.exit(1)
})
1 change: 1 addition & 0 deletions examples/helpers/examplesList.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ module.exports = Object.freeze([
'publicPermissions',
'transfer',
'getRegistrationStatuses',
'generateL1RegistrationPayload',
// TODO
// 'submitBuyOrder',
// 'submitSellOrder',
Expand Down
18 changes: 18 additions & 0 deletions examples/src/generateL1RegistrationPayload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const { starkKeyHex, ethAddress } = await rhinofi.getUserConfig()

const l1RegistrationSignature = await rhinofi.stark.signRegistration(
ethAddress
)

const callData = await rhinofi.stark.l1RegistrationCallData(
starkKeyHex,
ethAddress,
l1RegistrationSignature
)

logExampleResult({
ethAddress,
starkKeyHex,
sig: l1RegistrationSignature,
callData
})
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rhino.fi/client-js",
"version": "5.1.10",
"version": "5.2.0",
"main": "src/index.js",
"files": [
"src",
Expand Down Expand Up @@ -48,6 +48,7 @@
"@truffle/hdwallet-provider": "^2.0.13",
"env-cmd": "^10.1.0",
"jest": "^26.4.2",
"jest-environment-jsdom": "25",
"mustache": "^4.0.0",
"nock": "^13.0.4",
"solc": "^0.4.24"
Expand Down
21 changes: 21 additions & 0 deletions src/api/storeStarkL1Registration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Backup the Stark L1 registration payload for the currently logged in account
* @type {(dvf: ReturnType<import('../lib/dvf/bindApi')>,
* nonce: string,
* signature: string) => string}
*/
module.exports = async (dvf, nonce, signature) => {
const url = '/v1/trading/storeStarkL1Registration'

const { ethAddress } = await dvf.getUserConfig(nonce, signature)

const l1RegistrationSignature = await dvf.stark.signRegistration(
ethAddress
)

const data = {
l1RegistrationSignature
}

return dvf.postAuthenticated(url, nonce, signature, data)
}
47 changes: 47 additions & 0 deletions src/api/storeStarkL1Registration.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const nock = require('nock')
const instance = require('./test/helpers/instance')
const mockGetConf = require('./test/fixtures/getConf')

describe('dvf.storeStarkL1Registration', () => {
const tradingKey = process.env.PRIVATE_STARK_KEY
const config = {
wallet: {
type: 'tradingKey',
meta: {
starkPrivateKey: tradingKey
}
}
}
/**
* @param {(dvf: Awaited<ReturnType<typeof instance>) => Promise<unknown>} fn
*/
const withInstance = (fn) => async () => fn(await instance(config))

beforeAll(async () => {
mockGetConf()
})

afterAll(() => {
nock.restore()
})

it('Generates and submits the registration payload', withInstance(async (dvf) => {
mockGetConf()

const expectedBody = {
l1RegistrationSignature: '0x025e160f8936b367f1aa10f4602dd54a31831de28cfc4263cbfd6b2fd3e9328602421b80ed0520b9e4d20620339ecd34093f04cec933bdebddc31e3bfcd32e5504de195d61296b6ac602ab5db8d190b90cd1e767fe9d47d4c9d96ab62cf7ad41'
}

const payloadValidator = jest.fn(body => {
expect(body).toMatchObject(expectedBody)
return true
})

nock(dvf.config.api)
.post('/v1/trading/storeStarkL1Registration', payloadValidator)
.reply(200)

await dvf.storeStarkL1Registration(tradingKey)
expect(payloadValidator).toBeCalled()
}))
})
4 changes: 2 additions & 2 deletions src/api/test/helpers/instance.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ const getWeb3 = require('../../../../examples/helpers/getWeb3')

const RhinofiClientFactory = require('../../../index')

module.exports = async () => {
module.exports = async (configOverride = {}) => {
const rpcUrl = process.env.RPC_URL
const privateKey = process.env.PRIVATE_ETH_KEY

const { web3 } = getWeb3(privateKey, rpcUrl)

const gasStationApiKey = process.env.ETH_GAS_STATION_KEY || ''

const config = { gasStationApiKey }
const config = { gasStationApiKey, ...configOverride }

// It's possible to overwrite the API address with the testnet address
// for example like this:
Expand Down
13 changes: 13 additions & 0 deletions src/lib/dvf/bindApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,20 @@
*/
const _partial = require('lodash/partial')

/**
* Extracts the tail parameters from a function type, excluding the first parameter.
* @template F - The function type from which to extract parameters.
* @typedef {F extends (head: any, ...tail: infer R) => any ? R : never} ParametersExceptFirst
*/

module.exports = () => {
const dvf = {}

// returns a function that will call api functions prepending dvf
// as first argument
/**
* @type {<T extends Function>(fn: T) => (...args: ParametersExceptFirst<T>) => ReturnType<T>}
*/
const compose = (funk, ...args) => {
return _partial(funk, dvf, ...args)
}
Expand All @@ -30,6 +39,9 @@ module.exports = () => {
}

dvf.stark = {
signRegistration: compose(require('../stark/signRegistration')),
createRegistrationMessage: compose(require('../stark/createRegistrationMessage')),
l1RegistrationCallData: compose(require('../stark/l1RegistrationCallData')),
createOrder: compose(require('../stark/createOrder')),
createMarketOrder: compose(require('../stark/createMarketOrder')),
createOrderMessage: compose(require('../stark/createOrderMessage')),
Expand Down Expand Up @@ -269,6 +281,7 @@ module.exports = () => {
dvf.walletFailedEvent = compose(require('../../api/walletFailedEvent'))
dvf.walletSuccessEvent = compose(require('../../api/walletSuccessEvent'))
dvf.topPerformersTokens = compose(require('../../api/topPerformersTokens'))
dvf.storeStarkL1Registration = compose(require('../../api/storeStarkL1Registration'))

dvf.ledger = {
deposit: compose(require('../../api/ledger/deposit')),
Expand Down
5 changes: 4 additions & 1 deletion src/lib/keystore/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ module.exports = sw => starkPrivateKey => {
const sign = async tx => {
const starkKeyPair = await getKeyPair()

const starkMessage = getMessage(sw)(tx)
const starkMessage = typeof tx === 'string'
? tx
: getMessage(sw)(tx)

const signature = FP.mapValues(
x => '0x' + x,
starkSign({ sw }, starkKeyPair, starkMessage)
Expand Down
33 changes: 26 additions & 7 deletions src/lib/ledger/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,36 @@ const getMessage = sw => tx => {
return starkTransferTxToMessageHash(sw)(tx)
}

const withTransport = (dvf) => async (fn) => {
const Transport = selectTransport(dvf.isBrowser)
const transport = await Transport.create()
try {
return await fn(transport)
} finally {
await transport.close()
}
}

const getTxSignature = async (dvf, tx, path) => {
// Generic stark message Signing
if (typeof tx === 'string') {
return withTransport(dvf)(async (transport) => {
const eth = new Eth(transport)
const { address } = await eth.getAddress(path)
const starkPath = dvf.stark.ledger.getPath(address)
const paddedMessage = `0x${tx.padEnd(64, '0').substr(-64)}`
return eth.starkUnsafeSign(
starkPath,
paddedMessage
)
})
}

if (tx.type != null) {
if (!(transferTransactionTypes.includes(tx.type))) {
throw new DVFError(`Unsupported stark transaction type: ${tx.type}`, { tx })
}
let transport
try {
const Transport = selectTransport(dvf.isBrowser)
transport = await Transport.create()
return withTransport(dvf)(async (transport) => {
const eth = new Eth(transport)
const { address } = await eth.getAddress(path)
const starkPath = dvf.stark.ledger.getPath(address)
Expand Down Expand Up @@ -81,9 +102,7 @@ const getTxSignature = async (dvf, tx, path) => {
tx.type === 'ConditionalTransferRequest' ? tx.factRegistryAddress : null,
tx.type === 'ConditionalTransferRequest' ? tx.fact : null
)
} finally {
await transport.close()
}
})
} else {
const { starkSignature } = await createSignedOrder(
dvf, path, tx, { returnStarkPublicKey: false }
Expand Down
44 changes: 44 additions & 0 deletions src/lib/stark/createRegistrationMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const DVFError = require('../dvf/DVFError')
const sw = require('@rhino.fi/starkware-crypto')
const { utils } = require('web3')

// Constant from Starkware contract
// https://github.com/starkware-libs/starkex-contracts/blob/210bd5f6bcb6977211677821fe925140859a0f6e/scalable-dex/contracts/src/components/ECDSA.sol#L13-L14
const EC_ORDER = utils.BN('3618502788666131213697322783095070105526743751716087489154079457884512865583')

/**
* @type {(dvf: ReturnType<import('../dvf/bindApi')>,
* starkHex: string,
* ethAddress: string) => utils.BN}
*/
module.exports = (dvf, starkHex, ethAddress) => {
if (!ethAddress) {
throw new Error('ethAddress is required')
}

if (!starkHex) {
throw new Error('starkKeyHex is required')
}

/*
uint256 msgHash = uint256(
keccak256(abi.encodePacked("UserRegistration:", ethKey, starkKey))
) % ECDSA.EC_ORDER;
*/

try {
const hashedMessage = utils.soliditySha3(
utils.encodePacked(
{ value: 'UserRegistration:', type: 'string' },
{ value: ethAddress, type: 'address' },
{ value: starkHex, type: 'uint256' },
)
)

const message = utils.BN(hashedMessage).mod(EC_ORDER).toString(16)

return message
} catch (error) {
throw new DVFError('ERR_CREATING_STARK_REGISTRATION_MESSAGE', { error })
}
}
20 changes: 20 additions & 0 deletions src/lib/stark/l1RegistrationCallData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @type {(dvf: ReturnType<import('../dvf/bindApi')>,
* tradingKey: string,
* ethAddress: string,
* starkLRegistrationSignature: string) => string}
*/
module.exports = async (dvf, starkKeyHex, ethAddress, starkL1RegistrationSignature) => {
const starkExContract = new dvf.web3.eth.Contract(
dvf.contract.abi.getStarkEx(),
dvf.config.DVF.starkExContractAddress,
)

const callData = starkExContract.methods.registerEthAddress(
ethAddress,
starkKeyHex,
starkL1RegistrationSignature
).encodeABI()

return callData
}
Loading