Skip to content

Commit

Permalink
feat(account): new hash formula
Browse files Browse the repository at this point in the history
  • Loading branch information
janek26 committed Apr 19, 2022
1 parent f9eeee3 commit ea4df2c
Show file tree
Hide file tree
Showing 16 changed files with 32,277 additions and 38,835 deletions.
70,748 changes: 32,022 additions & 38,726 deletions __mocks__/ArgentAccount.json

Large diffs are not rendered by default.

78 changes: 46 additions & 32 deletions __tests__/accountContract.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Contract, defaultProvider, ec, hash, number, stark } from '../src';
import { StarknetChainId } from '../src/constants';
import {
calculcateTransactionHash,
getSelectorFromName,
transactionVersion,
} from '../src/utils/hash';
import { fromCallsToExecuteCalldata } from '../src/utils/transaction';
import { compiledArgentAccount, compiledErc20 } from './fixtures';

describe('getStarkAccountFromPrivateKey()', () => {
Expand All @@ -20,6 +27,45 @@ describe('getStarkAccountFromPrivateKey()', () => {
});
});

test('build tx', async () => {
const privateKey = '0x1B69B4BE052FAB1';
const keyPair = ec.getKeyPair(privateKey);
const address = ec.getStarkKey(keyPair);

expect(address).toBe('0x04024999b9574cb7623679ce049a609db62a95098982c5b28ac61abdebd1c82b');

const selector = hash.getSelectorFromName('transfer');

expect(selector).toBe(
number.toHex(
number.toBN('232670485425082704932579856502088130646006032362877466777181098476241604910')
)
);

const calls = [{ contractAddress: '1', entrypoint: 'transfer', calldata: ['6', '7'] }];
const calldata = [...fromCallsToExecuteCalldata(calls), '0'];

const msgHash = calculcateTransactionHash(
address,
transactionVersion,
getSelectorFromName('__execute__'),
calldata,
0,
StarknetChainId.TESTNET
);
expect(number.toBN(msgHash).toString()).toMatchInlineSnapshot(
`"235855380881994314533025886817815774848495061484535023348790852315407085619"`
);

const [r, s] = ec.sign(keyPair, msgHash);
expect(r.toString()).toMatchInlineSnapshot(
`"181489288548431284937202760565682158657883789985879744111612429574110648095"`
);
expect(s.toString()).toMatchInlineSnapshot(
`"2055384802167699202203509702082340762385659879831017273872106910763470114538"`
);
});

describe('deploy and test Wallet', () => {
const privateKey = stark.randomAddress();

Expand Down Expand Up @@ -64,35 +110,3 @@ describe('deploy and test Wallet', () => {
expect(res).toStrictEqual(number.toBN(1000));
});
});

test('build tx', async () => {
const privateKey = '0x1B69B4BE052FAB1';
const keyPair = ec.getKeyPair(privateKey);
const address = ec.getStarkKey(keyPair);

expect(address).toBe('0x04024999b9574cb7623679ce049a609db62a95098982c5b28ac61abdebd1c82b');

const selector = hash.getSelectorFromName('transfer');

expect(selector).toBe(
number.toHex(
number.toBN('232670485425082704932579856502088130646006032362877466777181098476241604910')
)
);

const calls = [{ contractAddress: '1', entrypoint: 'transfer', calldata: ['6', '7'] }];
const msgHash = hash.hashMulticall(address, calls, '0', '0');
expect(number.toBN(msgHash).toString()).toStrictEqual(
number
.toBN('533725737276146993132325070982049323585915612981489962412873515411469143806')
.toString()
);

const [r, s] = ec.sign(keyPair, msgHash);
expect(r.toString()).toBe(
'3081830197073374427897814075820860503521735760640862828374253887454357679197'
);
expect(s.toString()).toBe(
'384293936273611705317490990661155378189310283917528660618713929845936492551'
);
});
2 changes: 0 additions & 2 deletions __tests__/constancts.ts

This file was deleted.

26 changes: 20 additions & 6 deletions __tests__/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import { Account, Contract, ContractFactory, Provider, defaultProvider, ec, star
import { getSelectorFromName } from '../src/utils/hash';
import { BigNumberish, toBN } from '../src/utils/number';
import { compileCalldata } from '../src/utils/stark';
import { ACCOUNT_ADDRESS, PRIVATE_KEY } from './constancts';
import { compiledErc20, compiledMulticall, compiledTypeTransformation } from './fixtures';
import {
compiledArgentAccount,
compiledErc20,
compiledMulticall,
compiledTypeTransformation,
} from './fixtures';

describe('class Contract {}', () => {
const wallet = stark.randomAddress();
Expand Down Expand Up @@ -204,21 +208,29 @@ describe('class Contract {}', () => {
});

describe('Contract interaction with Account', () => {
const starkKeyPair = ec.getKeyPair(PRIVATE_KEY);
let account: Account;
let erc20: Contract;
let erc20Address: string;

beforeAll(async () => {
account = new Account(defaultProvider, ACCOUNT_ADDRESS, starkKeyPair);
const starkKeyPair = ec.genKeyPair();
const starkKeyPub = ec.getStarkKey(starkKeyPair);
const { address } = await defaultProvider.deployContract({
contract: compiledArgentAccount,
addressSalt: starkKeyPub,
});
expect(address).toBeDefined();
account = new Account(defaultProvider, address, starkKeyPair);
const accountContract = new Contract(compiledArgentAccount.abi, address);
await accountContract.initialize(starkKeyPub, '0');

const erc20Response = await defaultProvider.deployContract({
contract: compiledErc20,
});
erc20Address = erc20Response.address;
erc20 = new Contract(compiledErc20.abi, erc20Address, defaultProvider);
await defaultProvider.waitForTransaction(erc20Response.transaction_hash);
expect(erc20Response.code).toBe('TRANSACTION_RECEIVED');
await defaultProvider.waitForTransaction(erc20Response.transaction_hash);

const mintResponse = await erc20.mint(account.address, '1000');

Expand Down Expand Up @@ -251,7 +263,9 @@ describe('class Contract {}', () => {
});

test('invoke contract by wallet owner', async () => {
const { transaction_hash, code } = await erc20.transfer(toBN(erc20Address).toString(), 10);
const { transaction_hash, code } = await erc20.transfer(erc20Address, 10, {
maxFee: 0,
});
expect(code).toBe('TRANSACTION_RECEIVED');
await defaultProvider.waitForTransaction(transaction_hash);
const { res } = await erc20.balance_of(account.address);
Expand Down
4 changes: 2 additions & 2 deletions __tests__/utils/__snapshots__/utils.test.ts.snap

Large diffs are not rendered by default.

34 changes: 26 additions & 8 deletions __tests__/utils/ellipticalCurve.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { StarknetChainId } from '../../src/constants';
import { ec, getKeyPair, getStarkKey, sign, verify } from '../../src/utils/ellipticCurve';
import { removeHexPrefix } from '../../src/utils/encode';
import { computeHashOnElements, hashMulticall, pedersen } from '../../src/utils/hash';
import {
calculcateTransactionHash,
computeHashOnElements,
getSelectorFromName,
pedersen,
transactionVersion,
} from '../../src/utils/hash';
import { toBN, toHex } from '../../src/utils/number';
import { fromCallsToExecuteCalldata } from '../../src/utils/transaction';

test('getKeyPair()', () => {
const privateKey = '0x019800ea6a9a73f94aee6a3d2edf018fc770443e90c7ba121e8303ec6b349279';
Expand Down Expand Up @@ -42,17 +50,27 @@ test('hashMessage()', () => {
];
const nonce = '3';
const maxFee = '0';
const hashMsg = hashMulticall(account, transactions, nonce, maxFee);
expect(hashMsg).toBe(
toHex(toBN('1608351043472325350463069815257733118091727529101532499046754244230898025592'))
const calldata = [...fromCallsToExecuteCalldata(transactions), nonce];

const hashMsg = calculcateTransactionHash(
account,
transactionVersion,
getSelectorFromName('__execute__'),
calldata,
maxFee,
StarknetChainId.TESTNET
);

expect(hashMsg).toMatchInlineSnapshot(
`"0x4c337c6bf32b2cf2b8ae54064e4b982c214660e8d0423b431a3fde10b9b9c02"`
);
const keyPair = getKeyPair(privateKey);
const [r, s] = sign(keyPair, removeHexPrefix(hashMsg));
expect(r.toString()).toStrictEqual(
toBN('1079537730825246752292590270213864261175133133352510235773017189606850691611').toString()
expect(r.toString()).toMatchInlineSnapshot(
`"1944132633844378384908742523072599391732300777648030785844673145513474741467"`
);
expect(s.toString()).toStrictEqual(
toBN('2904560423220491364719171767721067837294296476624248675613584621502231297000').toString()
expect(s.toString()).toMatchInlineSnapshot(
`"1067771353159635307522498807851959257107695451405842425488451092336556917559"`
);
});

Expand Down
17 changes: 17 additions & 0 deletions __tests__/utils/transactionHash.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { StarknetChainId, TransactionHashPrefix } from '../../src/constants';
import { calculateTransactionHashCommon } from '../../src/utils/hash';

describe('calculateTransactionHashCommon()', () => {
test('should match most simple python output', () => {
const res = calculateTransactionHashCommon(
TransactionHashPrefix.INVOKE,
'0x0',
'0x2a',
'0x64',
[],
'0x0',
StarknetChainId.TESTNET
);
expect(res).toBe('0x7d260744de9d8c55e7675a34512d1951a7b262c79e685d26599edd2948de959');
});
});
12 changes: 6 additions & 6 deletions package-lock.json

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

38 changes: 22 additions & 16 deletions src/account/default.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import assert from 'minimalistic-assert';

import { ZERO } from '../constants';
import { Provider } from '../provider';
import { BlockIdentifier } from '../provider/utils';
import { Signer, SignerInterface } from '../signer';
Expand All @@ -19,10 +20,10 @@ import {
computeHashOnElements,
feeTransactionVersion,
getSelectorFromName,
transactionPrefix,
transactionVersion,
} from '../utils/hash';
import { BigNumberish, bigNumberishArrayToDecimalStringArray, toBN, toHex } from '../utils/number';
import { encodeShortString } from '../utils/shortString';
import { compileCalldata, estimatedFeeToMaxFee } from '../utils/stark';
import { fromCallsToExecuteCalldata } from '../utils/transaction';
import { TypedData, getMessageHash } from '../utils/typedData';
Expand Down Expand Up @@ -58,15 +59,16 @@ export class Account extends Provider implements AccountInterface {
const transactions = Array.isArray(calls) ? calls : [calls];
const nonce = providedNonce ?? (await this.getNonce());
const version = toBN(feeTransactionVersion);
const signerDetails = {

const signature = await this.signer.signTransaction(transactions, {
walletAddress: this.address,
nonce: toBN(nonce),
maxFee: toBN('0'),
maxFee: ZERO,
version,
};
const signature = await this.signer.signTransaction(transactions, signerDetails);
chainId: this.chainId,
});

const calldata = [...fromCallsToExecuteCalldata(transactions), signerDetails.nonce.toString()];
const calldata = [...fromCallsToExecuteCalldata(transactions), toBN(nonce).toString()];
return this.fetchEndpoint(
'estimate_fee',
{ blockIdentifier },
Expand Down Expand Up @@ -96,22 +98,26 @@ export class Account extends Provider implements AccountInterface {
const transactions = Array.isArray(calls) ? calls : [calls];
const nonce = toBN(transactionsDetail.nonce ?? (await this.getNonce()));
let maxFee: BigNumberish = '0';
if (transactionsDetail.maxFee) {
if (transactionsDetail.maxFee || transactionsDetail.maxFee === 0) {
maxFee = transactionsDetail.maxFee;
} else {
const estimatedFee = (await this.estimateFee(transactions, { nonce })).amount;
maxFee = estimatedFeeToMaxFee(estimatedFee).toString();
}
const signerDetails = {
walletAddress: this.address,
nonce,
maxFee,
version: toBN(transactionVersion),
};

const signature = await this.signer.signTransaction(transactions, signerDetails, abis);
const signature = await this.signer.signTransaction(
transactions,
{
walletAddress: this.address,
nonce,
maxFee,
version: toBN(transactionVersion),
chainId: this.chainId,
},
abis
);

const calldata = [...fromCallsToExecuteCalldata(transactions), signerDetails.nonce.toString()];
const calldata = [...fromCallsToExecuteCalldata(transactions), nonce.toString()];
return this.fetchEndpoint('add_transaction', undefined, {
type: 'INVOKE_FUNCTION',
contract_address: this.address,
Expand Down Expand Up @@ -161,7 +167,7 @@ export class Account extends Provider implements AccountInterface {
.map(computeHashOnElements);

return computeHashOnElements([
transactionPrefix,
encodeShortString('StarkNet Transaction'),
account,
computeHashOnElements(hashArray),
nonce,
Expand Down
10 changes: 10 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ export const TWO = toBN(2);
export const MASK_250 = TWO.pow(toBN(250)).sub(ONE); // 2 ** 250 - 1
export const MASK_251 = TWO.pow(toBN(251));

export enum StarknetChainId {
MAINNET = '0x534e5f4d41494e', // encodeShortString('SN_MAIN'),
TESTNET = '0x534e5f474f45524c49', // encodeShortString('SN_GOERLI'),
}
export enum TransactionHashPrefix {
DEPLOY = '0x6465706c6f79', // encodeShortString('deploy'),
INVOKE = '0x696e766f6b65', // encodeShortString('invoke'),
L1_HANDLER = '0x6c315f68616e646c6572', // encodeShortString('l1_handler'),
}

/**
* The following is taken from https://github.com/starkware-libs/starkex-resources/blob/master/crypto/starkware/crypto/signature/pedersen_params.json but converted to hex, because JS is very bad handling big integers by default
* Please do not edit until the JSON changes.
Expand Down
13 changes: 13 additions & 0 deletions src/provider/default.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import axios, { AxiosRequestHeaders } from 'axios';
import urljoin from 'url-join';

import { StarknetChainId } from '../constants';
import {
Abi,
AddTransactionResponse,
Expand Down Expand Up @@ -49,17 +50,22 @@ export class Provider implements ProviderInterface {

public gatewayUrl: string;

public chainId: StarknetChainId;

constructor(optionsOrProvider: ProviderOptions | Provider = { network: 'goerli-alpha' }) {
if (optionsOrProvider instanceof Provider) {
this.baseUrl = optionsOrProvider.baseUrl;
this.feederGatewayUrl = optionsOrProvider.feederGatewayUrl;
this.gatewayUrl = optionsOrProvider.gatewayUrl;
this.chainId =
optionsOrProvider.chainId ?? Provider.getChainIdFromBaseUrl(optionsOrProvider.baseUrl);
} else {
const baseUrl =
'baseUrl' in optionsOrProvider
? optionsOrProvider.baseUrl
: Provider.getNetworkFromName(optionsOrProvider.network);
this.baseUrl = baseUrl;
this.chainId = Provider.getChainIdFromBaseUrl(baseUrl);
this.feederGatewayUrl = urljoin(baseUrl, 'feeder_gateway');
this.gatewayUrl = urljoin(baseUrl, 'gateway');
}
Expand All @@ -75,6 +81,13 @@ export class Provider implements ProviderInterface {
}
}

protected static getChainIdFromBaseUrl(baseUrl: string): StarknetChainId {
if (baseUrl.includes('mainnet.starknet.io')) {
return StarknetChainId.MAINNET;
}
return StarknetChainId.TESTNET;
}

private getFetchUrl(endpoint: keyof Endpoints) {
const gatewayUrlEndpoints = ['add_transaction'];

Expand Down

0 comments on commit ea4df2c

Please sign in to comment.