Skip to content

Commit 8473adb

Browse files
committed
feat: ethereum signer
1 parent ac88085 commit 8473adb

File tree

5 files changed

+179
-0
lines changed

5 files changed

+179
-0
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export * as json from './utils/json';
2323
export * as num from './utils/num';
2424
export * as transaction from './utils/transaction';
2525
export * as stark from './utils/stark';
26+
export * as eth from './utils/eth';
2627
export * as merkle from './utils/merkle';
2728
export * as uint256 from './utils/uint256';
2829
export * as shortString from './utils/shortString';

src/signer/ethSigner.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { secp256k1 } from '@noble/curves/secp256k1';
2+
3+
import {
4+
Call,
5+
DeclareSignerDetails,
6+
DeployAccountSignerDetails,
7+
InvocationsSignerDetails,
8+
Signature,
9+
TypedData,
10+
V2DeclareSignerDetails,
11+
V2DeployAccountSignerDetails,
12+
V2InvocationsSignerDetails,
13+
V3DeclareSignerDetails,
14+
V3DeployAccountSignerDetails,
15+
V3InvocationsSignerDetails,
16+
} from '../types';
17+
import { ETransactionVersion2, ETransactionVersion3 } from '../types/api';
18+
import { CallData } from '../utils/calldata';
19+
import { addHexPrefix, buf2hex, removeHexPrefix, sanitizeHex } from '../utils/encode';
20+
import { ethRandomPrivateKey } from '../utils/eth';
21+
import {
22+
calculateDeclareTransactionHash,
23+
calculateDeployAccountTransactionHash,
24+
calculateInvokeTransactionHash,
25+
} from '../utils/hash';
26+
import { toHex } from '../utils/num';
27+
import { intDAM } from '../utils/stark';
28+
import { getExecuteCalldata } from '../utils/transaction';
29+
import { getMessageHash } from '../utils/typedData';
30+
import { SignerInterface } from './interface';
31+
32+
/**
33+
* Signer for accounts using Ethereum signature
34+
*/
35+
export class EthSigner implements SignerInterface {
36+
protected pk: string; // hex string without 0x and odd number of characters
37+
38+
constructor(pk: Uint8Array | string = ethRandomPrivateKey()) {
39+
this.pk =
40+
pk instanceof Uint8Array
41+
? removeHexPrefix(sanitizeHex(buf2hex(pk)))
42+
: removeHexPrefix(sanitizeHex(toHex(pk)));
43+
}
44+
45+
public async getPubKey(): Promise<string> {
46+
return addHexPrefix(buf2hex(secp256k1.getPublicKey(this.pk)));
47+
}
48+
49+
public async signMessage(typedData: TypedData, accountAddress: string): Promise<Signature> {
50+
const msgHash = getMessageHash(typedData, accountAddress);
51+
return secp256k1.sign(removeHexPrefix(sanitizeHex(msgHash)), this.pk);
52+
}
53+
54+
public async signTransaction(
55+
transactions: Call[],
56+
details: InvocationsSignerDetails
57+
): Promise<Signature> {
58+
const compiledCalldata = getExecuteCalldata(transactions, details.cairoVersion);
59+
let msgHash;
60+
61+
// TODO: How to do generic union discriminator for all like this
62+
if (Object.values(ETransactionVersion2).includes(details.version as any)) {
63+
const det = details as V2InvocationsSignerDetails;
64+
msgHash = calculateInvokeTransactionHash({
65+
...det,
66+
senderAddress: det.walletAddress,
67+
compiledCalldata,
68+
version: det.version,
69+
});
70+
} else if (Object.values(ETransactionVersion3).includes(details.version as any)) {
71+
const det = details as V3InvocationsSignerDetails;
72+
msgHash = calculateInvokeTransactionHash({
73+
...det,
74+
senderAddress: det.walletAddress,
75+
compiledCalldata,
76+
version: det.version,
77+
nonceDataAvailabilityMode: intDAM(det.nonceDataAvailabilityMode),
78+
feeDataAvailabilityMode: intDAM(det.feeDataAvailabilityMode),
79+
});
80+
} else {
81+
throw Error('unsupported signTransaction version');
82+
}
83+
84+
return secp256k1.sign(removeHexPrefix(sanitizeHex(msgHash)), this.pk);
85+
}
86+
87+
public async signDeployAccountTransaction(
88+
details: DeployAccountSignerDetails
89+
): Promise<Signature> {
90+
const compiledConstructorCalldata = CallData.compile(details.constructorCalldata);
91+
/* const version = BigInt(details.version).toString(); */
92+
let msgHash;
93+
94+
if (Object.values(ETransactionVersion2).includes(details.version as any)) {
95+
const det = details as V2DeployAccountSignerDetails;
96+
msgHash = calculateDeployAccountTransactionHash({
97+
...det,
98+
salt: det.addressSalt,
99+
constructorCalldata: compiledConstructorCalldata,
100+
version: det.version,
101+
});
102+
} else if (Object.values(ETransactionVersion3).includes(details.version as any)) {
103+
const det = details as V3DeployAccountSignerDetails;
104+
msgHash = calculateDeployAccountTransactionHash({
105+
...det,
106+
salt: det.addressSalt,
107+
compiledConstructorCalldata,
108+
version: det.version,
109+
nonceDataAvailabilityMode: intDAM(det.nonceDataAvailabilityMode),
110+
feeDataAvailabilityMode: intDAM(det.feeDataAvailabilityMode),
111+
});
112+
} else {
113+
throw Error('unsupported signDeployAccountTransaction version');
114+
}
115+
116+
return secp256k1.sign(removeHexPrefix(sanitizeHex(msgHash)), this.pk);
117+
}
118+
119+
public async signDeclareTransaction(
120+
// contractClass: ContractClass, // Should be used once class hash is present in ContractClass
121+
details: DeclareSignerDetails
122+
): Promise<Signature> {
123+
let msgHash;
124+
125+
if (Object.values(ETransactionVersion2).includes(details.version as any)) {
126+
const det = details as V2DeclareSignerDetails;
127+
msgHash = calculateDeclareTransactionHash({
128+
...det,
129+
version: det.version,
130+
});
131+
} else if (Object.values(ETransactionVersion3).includes(details.version as any)) {
132+
const det = details as V3DeclareSignerDetails;
133+
msgHash = calculateDeclareTransactionHash({
134+
...det,
135+
version: det.version,
136+
nonceDataAvailabilityMode: intDAM(det.nonceDataAvailabilityMode),
137+
feeDataAvailabilityMode: intDAM(det.feeDataAvailabilityMode),
138+
});
139+
} else {
140+
throw Error('unsupported signDeclareTransaction version');
141+
}
142+
143+
return secp256k1.sign(removeHexPrefix(sanitizeHex(msgHash)), this.pk);
144+
}
145+
}

src/signer/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './interface';
22
export * from './default';
3+
export * from './ethSigner';

src/utils/eth.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { secp256k1 } from '@noble/curves/secp256k1';
2+
3+
import { buf2hex, sanitizeHex } from './encode';
4+
5+
/**
6+
* Get random Ethereum private Key.
7+
* @returns an Hex string
8+
* @example
9+
* const myPK: string = randomAddress()
10+
* // result = "0xf04e69ac152fba37c02929c2ae78c9a481461dda42dbc6c6e286be6eb2a8ab83"
11+
*/
12+
export function ethRandomPrivateKey(): string {
13+
return sanitizeHex(buf2hex(secp256k1.utils.randomPrivateKey()));
14+
}

www/docs/guides/connect_account.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,21 @@ const account = new Account(provider, accountAddress, privateKey);
7777
// add ,"1" after privateKey if this account is not a Cairo 0 contract
7878

7979
```
80+
81+
## Connect to an account that uses Ethereum signature
82+
83+
As a consequence of account abstraction, you can find accounts that uses Ethereum signature logical.
84+
To connect to this type of account:
85+
86+
```typescript
87+
const myEthPrivateKey = "0x525bc68475c0955fae83869beec0996114d4bb27b28b781ed2a20ef23121b8de";
88+
const myEthAccountAddress = "0x65a822fbee1ae79e898688b5a4282dc79e0042cbed12f6169937fddb4c26641";
89+
const myEthSigner = new EthSigner(myEthPrivateKey);
90+
const myEthAccount = new Account(provider, myEthAccountAddress, myEthSigner)
91+
```
92+
93+
And if you need a randon Ethereum private key:
94+
95+
```typescript
96+
const myPrivateKey = eth.ethRandomPrivateKey();
97+
```

0 commit comments

Comments
 (0)