Skip to content

Commit

Permalink
feat: ethereum signer
Browse files Browse the repository at this point in the history
  • Loading branch information
PhilippeR26 committed Dec 15, 2023
1 parent ac88085 commit 8473adb
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export * as json from './utils/json';
export * as num from './utils/num';
export * as transaction from './utils/transaction';
export * as stark from './utils/stark';
export * as eth from './utils/eth';
export * as merkle from './utils/merkle';
export * as uint256 from './utils/uint256';
export * as shortString from './utils/shortString';
Expand Down
145 changes: 145 additions & 0 deletions src/signer/ethSigner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { secp256k1 } from '@noble/curves/secp256k1';

import {
Call,
DeclareSignerDetails,
DeployAccountSignerDetails,
InvocationsSignerDetails,
Signature,
TypedData,
V2DeclareSignerDetails,
V2DeployAccountSignerDetails,
V2InvocationsSignerDetails,
V3DeclareSignerDetails,
V3DeployAccountSignerDetails,
V3InvocationsSignerDetails,
} from '../types';
import { ETransactionVersion2, ETransactionVersion3 } from '../types/api';
import { CallData } from '../utils/calldata';
import { addHexPrefix, buf2hex, removeHexPrefix, sanitizeHex } from '../utils/encode';
import { ethRandomPrivateKey } from '../utils/eth';
import {
calculateDeclareTransactionHash,
calculateDeployAccountTransactionHash,
calculateInvokeTransactionHash,
} from '../utils/hash';
import { toHex } from '../utils/num';
import { intDAM } from '../utils/stark';
import { getExecuteCalldata } from '../utils/transaction';
import { getMessageHash } from '../utils/typedData';
import { SignerInterface } from './interface';

/**
* Signer for accounts using Ethereum signature
*/
export class EthSigner implements SignerInterface {
protected pk: string; // hex string without 0x and odd number of characters

constructor(pk: Uint8Array | string = ethRandomPrivateKey()) {
this.pk =
pk instanceof Uint8Array
? removeHexPrefix(sanitizeHex(buf2hex(pk)))
: removeHexPrefix(sanitizeHex(toHex(pk)));
}

public async getPubKey(): Promise<string> {
return addHexPrefix(buf2hex(secp256k1.getPublicKey(this.pk)));
}

public async signMessage(typedData: TypedData, accountAddress: string): Promise<Signature> {
const msgHash = getMessageHash(typedData, accountAddress);
return secp256k1.sign(removeHexPrefix(sanitizeHex(msgHash)), this.pk);
}

public async signTransaction(
transactions: Call[],
details: InvocationsSignerDetails
): Promise<Signature> {
const compiledCalldata = getExecuteCalldata(transactions, details.cairoVersion);
let msgHash;

// TODO: How to do generic union discriminator for all like this
if (Object.values(ETransactionVersion2).includes(details.version as any)) {
const det = details as V2InvocationsSignerDetails;
msgHash = calculateInvokeTransactionHash({
...det,
senderAddress: det.walletAddress,
compiledCalldata,
version: det.version,
});
} else if (Object.values(ETransactionVersion3).includes(details.version as any)) {
const det = details as V3InvocationsSignerDetails;
msgHash = calculateInvokeTransactionHash({
...det,
senderAddress: det.walletAddress,
compiledCalldata,
version: det.version,
nonceDataAvailabilityMode: intDAM(det.nonceDataAvailabilityMode),
feeDataAvailabilityMode: intDAM(det.feeDataAvailabilityMode),
});
} else {
throw Error('unsupported signTransaction version');
}

return secp256k1.sign(removeHexPrefix(sanitizeHex(msgHash)), this.pk);
}

public async signDeployAccountTransaction(
details: DeployAccountSignerDetails
): Promise<Signature> {
const compiledConstructorCalldata = CallData.compile(details.constructorCalldata);
/* const version = BigInt(details.version).toString(); */
let msgHash;

if (Object.values(ETransactionVersion2).includes(details.version as any)) {
const det = details as V2DeployAccountSignerDetails;
msgHash = calculateDeployAccountTransactionHash({
...det,
salt: det.addressSalt,
constructorCalldata: compiledConstructorCalldata,
version: det.version,
});
} else if (Object.values(ETransactionVersion3).includes(details.version as any)) {
const det = details as V3DeployAccountSignerDetails;
msgHash = calculateDeployAccountTransactionHash({
...det,
salt: det.addressSalt,
compiledConstructorCalldata,
version: det.version,
nonceDataAvailabilityMode: intDAM(det.nonceDataAvailabilityMode),
feeDataAvailabilityMode: intDAM(det.feeDataAvailabilityMode),
});
} else {
throw Error('unsupported signDeployAccountTransaction version');
}

return secp256k1.sign(removeHexPrefix(sanitizeHex(msgHash)), this.pk);
}

public async signDeclareTransaction(
// contractClass: ContractClass, // Should be used once class hash is present in ContractClass
details: DeclareSignerDetails
): Promise<Signature> {
let msgHash;

if (Object.values(ETransactionVersion2).includes(details.version as any)) {
const det = details as V2DeclareSignerDetails;
msgHash = calculateDeclareTransactionHash({
...det,
version: det.version,
});
} else if (Object.values(ETransactionVersion3).includes(details.version as any)) {
const det = details as V3DeclareSignerDetails;
msgHash = calculateDeclareTransactionHash({
...det,
version: det.version,
nonceDataAvailabilityMode: intDAM(det.nonceDataAvailabilityMode),
feeDataAvailabilityMode: intDAM(det.feeDataAvailabilityMode),
});
} else {
throw Error('unsupported signDeclareTransaction version');
}

return secp256k1.sign(removeHexPrefix(sanitizeHex(msgHash)), this.pk);
}
}
1 change: 1 addition & 0 deletions src/signer/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './interface';
export * from './default';
export * from './ethSigner';
14 changes: 14 additions & 0 deletions src/utils/eth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { secp256k1 } from '@noble/curves/secp256k1';

import { buf2hex, sanitizeHex } from './encode';

/**
* Get random Ethereum private Key.
* @returns an Hex string
* @example
* const myPK: string = randomAddress()
* // result = "0xf04e69ac152fba37c02929c2ae78c9a481461dda42dbc6c6e286be6eb2a8ab83"
*/
export function ethRandomPrivateKey(): string {
return sanitizeHex(buf2hex(secp256k1.utils.randomPrivateKey()));
}
18 changes: 18 additions & 0 deletions www/docs/guides/connect_account.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,21 @@ const account = new Account(provider, accountAddress, privateKey);
// add ,"1" after privateKey if this account is not a Cairo 0 contract

```

## Connect to an account that uses Ethereum signature

As a consequence of account abstraction, you can find accounts that uses Ethereum signature logical.
To connect to this type of account:

```typescript
const myEthPrivateKey = "0x525bc68475c0955fae83869beec0996114d4bb27b28b781ed2a20ef23121b8de";
const myEthAccountAddress = "0x65a822fbee1ae79e898688b5a4282dc79e0042cbed12f6169937fddb4c26641";
const myEthSigner = new EthSigner(myEthPrivateKey);
const myEthAccount = new Account(provider, myEthAccountAddress, myEthSigner)
```

And if you need a randon Ethereum private key:

```typescript
const myPrivateKey = eth.ethRandomPrivateKey();
```

0 comments on commit 8473adb

Please sign in to comment.