Skip to content

Commit

Permalink
feat: allow multi sig
Browse files Browse the repository at this point in the history
  • Loading branch information
janek26 committed Dec 13, 2021
1 parent a3813c9 commit fc1e086
Show file tree
Hide file tree
Showing 9 changed files with 48 additions and 30 deletions.
6 changes: 3 additions & 3 deletions __tests__/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ describe('deploy and test Wallet', () => {
)
);

const { r, s } = ec.sign(starkKeyPair, msgHash);
const signature = ec.sign(starkKeyPair, msgHash);
const { code, transaction_hash } = await wallet.invoke(
'execute',
{
Expand All @@ -113,7 +113,7 @@ describe('deploy and test Wallet', () => {
calldata: [erc20Address, '10'],
nonce: nonce.toString(),
},
[number.toHex(r), number.toHex(s)]
signature
);

expect(code).toBe('TRANSACTION_RECEIVED');
Expand Down Expand Up @@ -151,7 +151,7 @@ test('build tx', async () => {
.toString()
);

const { r, s } = ec.sign(keyPair, msgHash);
const [r, s] = ec.sign(keyPair, msgHash);
expect(r.toString()).toBe(
'706800951915233622090196542158919402159816118214143837213294331713137614072'
);
Expand Down
2 changes: 1 addition & 1 deletion __tests__/utils/ellipticalCurve.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ test('hashMessage()', () => {
);
expect(hashMsg).toBe('0x7f15c38ea577a26f4f553282fcfe4f1feeb8ecfaad8f221ae41abf8224cbddd');
const keyPair = getKeyPair(privateKey);
const { r, s } = sign(keyPair, removeHexPrefix(hashMsg));
const [r, s] = sign(keyPair, removeHexPrefix(hashMsg));
expect(r.toString()).toStrictEqual(
toBN('2458502865976494910213617956670505342647705497324144349552978333078363662855').toString()
);
Expand Down
4 changes: 2 additions & 2 deletions src/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import BN from 'bn.js';
import assert from 'minimalistic-assert';

import { Provider, defaultProvider } from './provider';
import { Abi, AbiEntry, FunctionAbi, StructAbi } from './types';
import { Abi, AbiEntry, FunctionAbi, Signature, StructAbi } from './types';
import { BigNumberish, toBN } from './utils/number';
import { getSelectorFromName } from './utils/stark';

Expand Down Expand Up @@ -146,7 +146,7 @@ export class Contract {
return this.parseResponseField(methodAbi, responseIterator);
}

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

Expand Down
10 changes: 5 additions & 5 deletions src/provider/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
GetContractAddressesResponse,
GetTransactionResponse,
GetTransactionStatusResponse,
Signature,
Transaction,
} from '../types';
import { parse, stringify } from '../utils/json';
Expand Down Expand Up @@ -265,7 +266,7 @@ export class Provider implements ProviderInterface {
contractAddress: string,
entrypointSelector: string,
calldata?: string[],
signature?: [BigNumberish, BigNumberish]
signature?: Signature
): Promise<AddTransactionResponse> {
return this.addTransaction({
type: 'INVOKE_FUNCTION',
Expand All @@ -278,16 +279,15 @@ export class Provider implements ProviderInterface {

public async waitForTx(txHash: BigNumberish, retryInterval: number = 8000) {
let onchain = false;
await wait(retryInterval);

while (!onchain) {
// eslint-disable-next-line no-await-in-loop
await wait(retryInterval);
// eslint-disable-next-line no-await-in-loop
const res = await this.getTransactionStatus(txHash);

if (
res.tx_status === 'ACCEPTED_ONCHAIN' ||
(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.
) {
if (res.tx_status === 'ACCEPTED_ON_L1' || res.tx_status === 'ACCEPTED_ON_L2') {
onchain = true;
} else if (res.tx_status === 'REJECTED') {
throw Error('REJECTED');
Expand Down
3 changes: 2 additions & 1 deletion src/provider/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
GetContractAddressesResponse,
GetTransactionResponse,
GetTransactionStatusResponse,
Signature,
Transaction,
} from '../types';
import type { BigNumberish } from '../utils/number';
Expand Down Expand Up @@ -135,7 +136,7 @@ export abstract class ProviderInterface {
contractAddress: string,
entrypointSelector: string,
calldata?: string[],
signature?: [BigNumberish, BigNumberish]
signature?: Signature
): Promise<AddTransactionResponse>;

public abstract waitForTx(txHash: BigNumberish, retryInterval?: number): Promise<void>;
Expand Down
4 changes: 2 additions & 2 deletions src/signer/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class Signer extends Provider implements SignerInterface {
)
);

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

return super.addTransaction({
type: 'INVOKE_FUNCTION',
Expand All @@ -67,7 +67,7 @@ export class Signer extends Provider implements SignerInterface {
nonceBn.toString(),
].map((x) => toBN(x).toString()),
contract_address: this.address,
signature: [r, s],
signature,
});
}

Expand Down
12 changes: 9 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ import type { ec as EC } from 'elliptic';
import type { BigNumberish } from './utils/number';

export type KeyPair = EC.KeyPair;
export type Signature = EC.Signature;
export type Signature = BigNumberish[];

export type GetContractAddressesResponse = {
Starknet: string;
GpsStatementVerifier: string;
};

export type Status = 'NOT_RECEIVED' | 'RECEIVED' | 'PENDING' | 'REJECTED' | 'ACCEPTED_ONCHAIN';
export type Status =
| 'NOT_RECEIVED'
| 'RECEIVED'
| 'PENDING'
| 'ACCEPTED_ON_L1'
| 'ACCEPTED_ON_L2'
| 'REJECTED';
export type TransactionStatus = 'TRANSACTION_RECEIVED';
export type Type = 'DEPLOY' | 'INVOKE_FUNCTION';
export type EntryPointType = 'EXTERNAL';
Expand Down Expand Up @@ -56,7 +62,7 @@ export type DeployTransaction = {
export type InvokeFunctionTransaction = {
type: 'INVOKE_FUNCTION';
contract_address: string;
signature?: [BigNumberish, BigNumberish];
signature?: Signature;
entry_point_type?: EntryPointType;
entry_point_selector: string;
calldata?: string[];
Expand Down
29 changes: 20 additions & 9 deletions src/utils/ellipticCurve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ export function sign(keyPair: KeyPair, msgHash: string): Signature {
assertInRange(r, ONE, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'r');
assertInRange(s, ONE, toBN(addHexPrefix(EC_ORDER)), 's');
assertInRange(w, ONE, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'w');
return msgSignature;
return [r, s];
}

function chunkArray(arr: any[], n: number): any[][] {
return Array(Math.ceil(arr.length / n))
.fill('')
.map((_, i) => arr.slice(i * n, i * n + n));
}

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

return keyPair.verify(fixMessage(msgHash), sig);
return chunkArray(sig, 2).every(([r, s], i) => {
const rBN = toBN(r);
const sBN = toBN(s);
const w = sBN.invm((ec as any).n);
assertInRange(rBN, ONE, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'r');
assertInRange(sBN, ONE, toBN(addHexPrefix(EC_ORDER)), 's');
assertInRange(w, ONE, toBN(addHexPrefix(MAX_ECDSA_VAL)), 'w');
return ec.verify(fixMessage(msgHash), { r: rBN, s: sBN }, keyPairArray[i]) ?? false;
});
}
8 changes: 4 additions & 4 deletions src/utils/stark.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { gzip } from 'pako';

import { CompressedProgram, Program } from '../types';
import { CompressedProgram, Program, Signature } from '../types';
import { genKeyPair, getStarkKey } from './ellipticCurve';
import { addHexPrefix, btoaUniversal } from './encode';
import { starknetKeccak } from './hash';
import { stringify } from './json';
import { BigNumberish, toBN, toHex } from './number';
import { toBN, toHex } from './number';

/**
* Function to compress compiled cairo program
Expand Down Expand Up @@ -41,10 +41,10 @@ export function makeAddress(input: string): string {
return addHexPrefix(input).toLowerCase();
}

export function formatSignature(sig?: [BigNumberish, BigNumberish]): [string, string] | [] {
export function formatSignature(sig?: Signature): string[] {
if (!sig) return [];
try {
return sig.map((x) => toBN(x)).map((x) => x.toString()) as [string, string];
return sig.map((x) => toBN(x)).map((x) => x.toString());
} catch (e) {
return [];
}
Expand Down

0 comments on commit fc1e086

Please sign in to comment.