Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions __tests__/utils/hash.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { keccakBn, starknetKeccak, getSelectorFromName, getSelector } from '../../src/utils/hash';

describe('keccakBn', () => {
test('should properly calculate the Keccak hash', () => {
expect(keccakBn('0xabc')).toBe(
'0x11cf08aac85935e32397f410e48217a127b6855d41b1e3877eb4179c0904b77'
);
});
});

describe('starknetKeccak', () => {
test('should properly calculate the starknet BigInt Keccak hash', () => {
expect(starknetKeccak('test').toString()).toBe(
'61835310290161785288773114225739080147441215596947647498723774891619563096'
);
});
});

describe('getSelectorFromName', () => {
test('should properly calculate the selector', () => {
expect(getSelectorFromName('myFunction')).toBe(
'0xc14cfe23f3fa7ce7b1f8db7d7682305b1692293f71a61cc06637f0d8d8b6c8'
);
});
});

describe('getSelector', () => {
test('should return the proper selector when provided a function name', () => {
expect(getSelector('myFunction')).toBe(
'0xc14cfe23f3fa7ce7b1f8db7d7682305b1692293f71a61cc06637f0d8d8b6c8'
);
});

test('should return the proper selector when provided a hex-string', () => {
expect(getSelector('0x123abc')).toBe('0x123abc');
});

test('should return the proper selector when provided a decimal string', () => {
expect(getSelector('123456')).toBe('0x1e240');
});
});
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export * as typedData from './utils/typedData';
export * as ec from './utils/ec';
export * as starknetId from './utils/starknetId';
export * as provider from './utils/provider';
export * as selector from './utils/selector';
export * as selector from './utils/hash/selector';
export * as events from './utils/events/index';
export * from './utils/cairoDataTypes/uint256';
export * from './utils/cairoDataTypes/uint512';
Expand Down
2 changes: 1 addition & 1 deletion src/utils/calldata/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from '../../types';
import assert from '../assert';
import { isBigInt, toHex } from '../num';
import { getSelectorFromName } from '../selector';
import { getSelectorFromName } from '../hash/selector';
import { isLongText } from '../shortString';
import { byteArrayFromString } from './byteArray';
import { felt, isCairo1Type, isLen } from './cairo';
Expand Down
2 changes: 1 addition & 1 deletion src/utils/hash/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Hashes Exports
*/
export * as poseidon from '@noble/curves/abstract/poseidon';
export * from '../selector'; // Preserve legacy export structure
export * from './selector'; // Preserve legacy export structure

export * from './transactionHash';
export * from './classHash';
96 changes: 96 additions & 0 deletions src/utils/hash/selector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { keccak } from '@scure/starknet';

import { MASK_250 } from '../../constants';
import { BigNumberish } from '../../types';
import { addHexPrefix, removeHexPrefix, utf8ToArray } from '../encode';
import { hexToBytes, isHex, isStringWholeNumber, toHex, toHexString } from '../num';

/**
* Calculate the hex-string Keccak hash for a given BigNumberish
*
* @param value value to hash
* @returns hex-string Keccak hash
* @example
* ```typescript
* const result = keccakBn('0xabc');
* // result = '0x11cf08aac85935e32397f410e48217a127b6855d41b1e3877eb4179c0904b77'
* ```
*/
export function keccakBn(value: BigNumberish): string {
const hexWithoutPrefix = removeHexPrefix(toHex(BigInt(value)));
const evenHex = hexWithoutPrefix.length % 2 === 0 ? hexWithoutPrefix : `0${hexWithoutPrefix}`;
return addHexPrefix(keccak(hexToBytes(addHexPrefix(evenHex))).toString(16));
}

/**
* [internal]
* Calculate hex-string Keccak hash for a given string
*
* String -> hex-string Keccak hash
* @returns format: hex-string
*/
function keccakHex(str: string): string {
return addHexPrefix(keccak(utf8ToArray(str)).toString(16));
}

/**
* Calculate the BigInt Starknet Keccak hash for a given string
* [Reference](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/public/abi.py#L38)
*
* @param str value to hash
* @returns BigInt Keccak hash
* @example
* ```typescript
* const result = starknetKeccak('test').toString();
* // result = '61835310290161785288773114225739080147441215596947647498723774891619563096'
* ```
*/
export function starknetKeccak(str: string): bigint {
const hash = BigInt(keccakHex(str));
// eslint-disable-next-line no-bitwise
return hash & MASK_250;
}

/**
* Calculate the hex-string selector for a given abi function name
* [Reference](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/public/abi.py#L46)
*
* @param funcName abi function name
* @returns hex-string selector
* @example
* ```typescript
* const result = getSelectorFromName('myFunction');
* // result = '0xc14cfe23f3fa7ce7b1f8db7d7682305b1692293f71a61cc06637f0d8d8b6c8'
* ```
*/
export function getSelectorFromName(funcName: string) {
// sometimes BigInteger pads the hex string with zeros, which is not allowed in the starknet api
return toHex(starknetKeccak(funcName));
}

/**
* Calculate the hex-string selector from a given abi function name, decimal string or hex string
*
* @param value hex-string | dec-string | ascii-string
* @returns hex-string selector
* @example
* ```typescript
* const selector1: string = getSelector("myFunction");
* // selector1 = "0xc14cfe23f3fa7ce7b1f8db7d7682305b1692293f71a61cc06637f0d8d8b6c8"
*
* const selector2: string = getSelector("0x123abc");
* // selector2 = "0x123abc"
*
* const selector3: string = getSelector("123456");
* // selector3 = "0x1e240"
* ```
*/
export function getSelector(value: string) {
if (isHex(value)) {
return value;
}
if (isStringWholeNumber(value)) {
return toHexString(value);
}
return getSelectorFromName(value);
}
74 changes: 63 additions & 11 deletions src/utils/merkle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,24 @@ export class MerkleTree {

public hashMethod: (a: BigNumberish, b: BigNumberish) => string;

/**
* Create a Merkle tree
*
* @param leafHashes hex-string array
* @param hashMethod hash method to use, default: Pedersen
* @returns created Merkle tree
* @example
* ```typescript
* const leaves = ['0x1', '0x2', '0x3', '0x4', '0x5', '0x6', '0x7'];
* const tree = new MerkleTree(leaves);
* // tree = {
* // branches: [['0x5bb9440e2...', '0x262697b88...', ...], ['0x38118a340...', ...], ...],
* // leaves: ['0x1', '0x2', '0x3', '0x4', '0x5', '0x6', '0x7'],
* // root: '0x7f748c75e5bdb7ae28013f076b8ab650c4e01d3530c6e5ab665f9f1accbe7d4',
* // hashMethod: [Function computePedersenHash],
* // }
* ```
*/
constructor(
leafHashes: string[],
hashMethod: (a: BigNumberish, b: BigNumberish) => string = computePedersenHash
Expand All @@ -19,11 +37,7 @@ export class MerkleTree {
this.root = this.build(leafHashes);
}

/**
* Create Merkle tree
* @param leaves hex-string array
* @returns format: hex-string; Merkle tree root
*/
/** @ignore */
private build(leaves: string[]): string {
if (leaves.length === 1) {
return leaves[0];
Expand All @@ -43,8 +57,21 @@ export class MerkleTree {
}

/**
* Create hash from ordered a and b, Pedersen hash default
* @returns format: hex-string
* Calculate hash from ordered a and b, Pedersen hash default
*
* @param a first value
* @param b second value
* @param hashMethod hash method to use, default: Pedersen
* @returns result of the hash function
* @example
* ```typescript
* const result1 = MerkleTree.hash('0xabc', '0xdef');
* // result1 = '0x484f029da7914ada038b1adf67fc83632364a3ebc2cd9349b41ab61626d9e82'
*
* const customHashMethod = (a, b) => `custom_${a}_${b}`;
* const result2 = MerkleTree.hash('0xabc', '0xdef', customHashMethod);
* // result2 = 'custom_2748_3567'
* ```
*/
static hash(
a: BigNumberish,
Expand All @@ -56,11 +83,23 @@ export class MerkleTree {
}

/**
* Return path to leaf
* Calculates the merkle membership proof path
*
* @param leaf hex-string
* @param branch hex-string array
* @param hashPath hex-string array
* @returns format: hex-string array
* @returns collection of merkle proof hex-string hashes
* @example
* ```typescript
* const leaves = ['0x1', '0x2', '0x3', '0x4', '0x5', '0x6', '0x7'];
* const tree = new MerkleTree(leaves);
* const result = tree.getProof('0x3');
* // result = [
* // '0x4',
* // '0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026',
* // '0x8c0e46dd2df9aaf3a8ebfbc25408a582ad7fa7171f0698ddbbc5130b4b4e60',
* // ]
* ```
*/
public getProof(leaf: string, branch = this.leaves, hashPath: string[] = []): string[] {
const index = branch.indexOf(leaf);
Expand All @@ -87,11 +126,24 @@ export class MerkleTree {
}

/**
* Test Merkle tree path
* Tests a Merkle tree path
*
* @param root hex-string
* @param leaf hex-string
* @param path hex-string array
* @param hashMethod hash method override, Pedersen default
* @param hashMethod hash method to use, default: Pedersen
* @returns true if the path is valid, false otherwise
* @example
* ```typescript
* const leaves = ['0x1', '0x2', '0x3', '0x4', '0x5', '0x6', '0x7'];
* const tree = new MerkleTree(leaves);
* const result = proofMerklePath(tree.root, '0x3', [
* '0x4',
* '0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026',
* '0x8c0e46dd2df9aaf3a8ebfbc25408a582ad7fa7171f0698ddbbc5130b4b4e60',
* ]);
* // result = true
* ```
*/
export function proofMerklePath(
root: string,
Expand Down
86 changes: 0 additions & 86 deletions src/utils/selector.ts

This file was deleted.