Skip to content

Commit fc1e086

Browse files
committed
feat: allow multi sig
1 parent a3813c9 commit fc1e086

File tree

9 files changed

+48
-30
lines changed

9 files changed

+48
-30
lines changed

__tests__/account.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ describe('deploy and test Wallet', () => {
104104
)
105105
);
106106

107-
const { r, s } = ec.sign(starkKeyPair, msgHash);
107+
const signature = ec.sign(starkKeyPair, msgHash);
108108
const { code, transaction_hash } = await wallet.invoke(
109109
'execute',
110110
{
@@ -113,7 +113,7 @@ describe('deploy and test Wallet', () => {
113113
calldata: [erc20Address, '10'],
114114
nonce: nonce.toString(),
115115
},
116-
[number.toHex(r), number.toHex(s)]
116+
signature
117117
);
118118

119119
expect(code).toBe('TRANSACTION_RECEIVED');
@@ -151,7 +151,7 @@ test('build tx', async () => {
151151
.toString()
152152
);
153153

154-
const { r, s } = ec.sign(keyPair, msgHash);
154+
const [r, s] = ec.sign(keyPair, msgHash);
155155
expect(r.toString()).toBe(
156156
'706800951915233622090196542158919402159816118214143837213294331713137614072'
157157
);

__tests__/utils/ellipticalCurve.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ test('hashMessage()', () => {
4040
);
4141
expect(hashMsg).toBe('0x7f15c38ea577a26f4f553282fcfe4f1feeb8ecfaad8f221ae41abf8224cbddd');
4242
const keyPair = getKeyPair(privateKey);
43-
const { r, s } = sign(keyPair, removeHexPrefix(hashMsg));
43+
const [r, s] = sign(keyPair, removeHexPrefix(hashMsg));
4444
expect(r.toString()).toStrictEqual(
4545
toBN('2458502865976494910213617956670505342647705497324144349552978333078363662855').toString()
4646
);

src/contract.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import BN from 'bn.js';
22
import assert from 'minimalistic-assert';
33

44
import { Provider, defaultProvider } from './provider';
5-
import { Abi, AbiEntry, FunctionAbi, StructAbi } from './types';
5+
import { Abi, AbiEntry, FunctionAbi, Signature, StructAbi } from './types';
66
import { BigNumberish, toBN } from './utils/number';
77
import { getSelectorFromName } from './utils/stark';
88

@@ -146,7 +146,7 @@ export class Contract {
146146
return this.parseResponseField(methodAbi, responseIterator);
147147
}
148148

149-
public invoke(method: string, args: Args = {}, signature?: [BigNumberish, BigNumberish]) {
149+
public invoke(method: string, args: Args = {}, signature?: Signature) {
150150
// ensure contract is connected
151151
assert(this.connectedTo !== null, 'contract isnt connected to an address');
152152

src/provider/default.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
GetContractAddressesResponse,
1212
GetTransactionResponse,
1313
GetTransactionStatusResponse,
14+
Signature,
1415
Transaction,
1516
} from '../types';
1617
import { parse, stringify } from '../utils/json';
@@ -265,7 +266,7 @@ export class Provider implements ProviderInterface {
265266
contractAddress: string,
266267
entrypointSelector: string,
267268
calldata?: string[],
268-
signature?: [BigNumberish, BigNumberish]
269+
signature?: Signature
269270
): Promise<AddTransactionResponse> {
270271
return this.addTransaction({
271272
type: 'INVOKE_FUNCTION',
@@ -278,16 +279,15 @@ export class Provider implements ProviderInterface {
278279

279280
public async waitForTx(txHash: BigNumberish, retryInterval: number = 8000) {
280281
let onchain = false;
282+
await wait(retryInterval);
283+
281284
while (!onchain) {
282285
// eslint-disable-next-line no-await-in-loop
283286
await wait(retryInterval);
284287
// eslint-disable-next-line no-await-in-loop
285288
const res = await this.getTransactionStatus(txHash);
286289

287-
if (
288-
res.tx_status === 'ACCEPTED_ONCHAIN' ||
289-
(res.tx_status === 'PENDING' && res.block_hash !== 'pending') // This is needed as of today. In the future there will be a different status for pending transactions.
290-
) {
290+
if (res.tx_status === 'ACCEPTED_ON_L1' || res.tx_status === 'ACCEPTED_ON_L2') {
291291
onchain = true;
292292
} else if (res.tx_status === 'REJECTED') {
293293
throw Error('REJECTED');

src/provider/interface.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
GetContractAddressesResponse,
99
GetTransactionResponse,
1010
GetTransactionStatusResponse,
11+
Signature,
1112
Transaction,
1213
} from '../types';
1314
import type { BigNumberish } from '../utils/number';
@@ -135,7 +136,7 @@ export abstract class ProviderInterface {
135136
contractAddress: string,
136137
entrypointSelector: string,
137138
calldata?: string[],
138-
signature?: [BigNumberish, BigNumberish]
139+
signature?: Signature
139140
): Promise<AddTransactionResponse>;
140141

141142
public abstract waitForTx(txHash: BigNumberish, retryInterval?: number): Promise<void>;

src/signer/default.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export class Signer extends Provider implements SignerInterface {
5454
)
5555
);
5656

57-
const { r, s } = sign(this.keyPair, msgHash);
57+
const signature = sign(this.keyPair, msgHash);
5858

5959
return super.addTransaction({
6060
type: 'INVOKE_FUNCTION',
@@ -67,7 +67,7 @@ export class Signer extends Provider implements SignerInterface {
6767
nonceBn.toString(),
6868
].map((x) => toBN(x).toString()),
6969
contract_address: this.address,
70-
signature: [r, s],
70+
signature,
7171
});
7272
}
7373

src/types.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@ import type { ec as EC } from 'elliptic';
33
import type { BigNumberish } from './utils/number';
44

55
export type KeyPair = EC.KeyPair;
6-
export type Signature = EC.Signature;
6+
export type Signature = BigNumberish[];
77

88
export type GetContractAddressesResponse = {
99
Starknet: string;
1010
GpsStatementVerifier: string;
1111
};
1212

13-
export type Status = 'NOT_RECEIVED' | 'RECEIVED' | 'PENDING' | 'REJECTED' | 'ACCEPTED_ONCHAIN';
13+
export type Status =
14+
| 'NOT_RECEIVED'
15+
| 'RECEIVED'
16+
| 'PENDING'
17+
| 'ACCEPTED_ON_L1'
18+
| 'ACCEPTED_ON_L2'
19+
| 'REJECTED';
1420
export type TransactionStatus = 'TRANSACTION_RECEIVED';
1521
export type Type = 'DEPLOY' | 'INVOKE_FUNCTION';
1622
export type EntryPointType = 'EXTERNAL';
@@ -56,7 +62,7 @@ export type DeployTransaction = {
5662
export type InvokeFunctionTransaction = {
5763
type: 'INVOKE_FUNCTION';
5864
contract_address: string;
59-
signature?: [BigNumberish, BigNumberish];
65+
signature?: Signature;
6066
entry_point_type?: EntryPointType;
6167
entry_point_selector: string;
6268
calldata?: string[];

src/utils/ellipticCurve.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,13 @@ export function sign(keyPair: KeyPair, msgHash: string): Signature {
6969
assertInRange(r, ONE, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'r');
7070
assertInRange(s, ONE, toBN(addHexPrefix(EC_ORDER)), 's');
7171
assertInRange(w, ONE, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'w');
72-
return msgSignature;
72+
return [r, s];
73+
}
74+
75+
function chunkArray(arr: any[], n: number): any[][] {
76+
return Array(Math.ceil(arr.length / n))
77+
.fill('')
78+
.map((_, i) => arr.slice(i * n, i * n + n));
7379
}
7480

7581
/*
@@ -78,15 +84,20 @@ export function sign(keyPair: KeyPair, msgHash: string): Signature {
7884
msgSignature should be an Signature.
7985
Returns a boolean true if the verification succeeds.
8086
*/
81-
export function verify(keyPair: KeyPair, msgHash: string, sig: Signature): boolean {
87+
export function verify(keyPair: KeyPair | KeyPair[], msgHash: string, sig: Signature): boolean {
88+
const keyPairArray = Array.isArray(keyPair) ? keyPair : [keyPair];
8289
const msgHashBN = toBN(addHexPrefix(msgHash));
90+
assert(sig.length % 2 === 0, 'Signature must be an array of length dividable by 2');
8391
assertInRange(msgHashBN, ZERO, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'msgHash');
84-
const { r, s } = sig;
85-
const w = s.invm(ec.n!);
86-
// Verify signature has valid length.
87-
assertInRange(r, ONE, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'r');
88-
assertInRange(s, ONE, toBN(addHexPrefix(EC_ORDER)), 's');
89-
assertInRange(w, ONE, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'w');
92+
assert(keyPairArray.length === sig.length / 2, 'Signature and keyPair length must be equal');
9093

91-
return keyPair.verify(fixMessage(msgHash), sig);
94+
return chunkArray(sig, 2).every(([r, s], i) => {
95+
const rBN = toBN(r);
96+
const sBN = toBN(s);
97+
const w = sBN.invm((ec as any).n);
98+
assertInRange(rBN, ONE, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'r');
99+
assertInRange(sBN, ONE, toBN(addHexPrefix(EC_ORDER)), 's');
100+
assertInRange(w, ONE, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'w');
101+
return ec.verify(fixMessage(msgHash), { r: rBN, s: sBN }, keyPairArray[i]) ?? false;
102+
});
92103
}

src/utils/stark.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { gzip } from 'pako';
22

3-
import { CompressedProgram, Program } from '../types';
3+
import { CompressedProgram, Program, Signature } from '../types';
44
import { genKeyPair, getStarkKey } from './ellipticCurve';
55
import { addHexPrefix, btoaUniversal } from './encode';
66
import { starknetKeccak } from './hash';
77
import { stringify } from './json';
8-
import { BigNumberish, toBN, toHex } from './number';
8+
import { toBN, toHex } from './number';
99

1010
/**
1111
* Function to compress compiled cairo program
@@ -41,10 +41,10 @@ export function makeAddress(input: string): string {
4141
return addHexPrefix(input).toLowerCase();
4242
}
4343

44-
export function formatSignature(sig?: [BigNumberish, BigNumberish]): [string, string] | [] {
44+
export function formatSignature(sig?: Signature): string[] {
4545
if (!sig) return [];
4646
try {
47-
return sig.map((x) => toBN(x)).map((x) => x.toString()) as [string, string];
47+
return sig.map((x) => toBN(x)).map((x) => x.toString());
4848
} catch (e) {
4949
return [];
5050
}

0 commit comments

Comments
 (0)