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
2 changes: 1 addition & 1 deletion src/Cairo/Felt.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static function assertInCairoVMRange($value)
throw new InvalidArgumentException('The value to assertInCairoVMRange function in not support.');
}
if (
!($value->compare(Constants::ZERO()) >= 0 && $value->compare(Utils::toBn(Constants::FIELD_PRIME)) < 0)
!($value->compare(Constants::ZERO()) >= 0 && $value->compare(Utils::toBn('0x' . Constants::FIELD_PRIME)) < 0)
) {
throw new InvalidArgumentException('The value in expected to be in the range [0,' . Constants::FIELD_PRIME . '].');
}
Expand Down
171 changes: 171 additions & 0 deletions src/Crypto/PoseidonHash.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<?php

/**
* This file is part of starknet.php package.
*
* (c) Kuan-Cheng,Lai <alk03073135@gmail.com>
*
* @author Peter Lai <alk03073135@gmail.com>
* @license MIT
*/

namespace StarkNet\Crypto;

use StarkNet\Utils;
use StarkNet\Constants;

class PoseidonHash
{
const RATE = 2;
const CAPACITY = 1;
const ROUNDS_FULL = 8;
const ROUNDS_PARTIAL = 83;

private static $fieldPrime = null;
private static $roundConstants = null;
private static $mds = null;

private static function fieldPrime()
{
if (self::$fieldPrime === null) {
self::$fieldPrime = Utils::toBn('0x' . Constants::FIELD_PRIME, 16);
}
return self::$fieldPrime;
}

private static function field($value)
{
if (!($value instanceof BigInteger)) {
$value = Utils::toBn($value);
}

list(, $remainder) = $value->divide(self::fieldPrime());

if ($remainder->is_negative) {
$remainder = $remainder->add(self::fieldPrime());
}

return $remainder;
}

private static function roundConstant($name, $index)
{
return self::field(Utils::toBn('0x' . hash('sha256', $name . (string) $index)));
}

private static function roundConstants()
{
if (self::$roundConstants === null) {
self::$roundConstants = array();
$rounds = self::ROUNDS_FULL + self::ROUNDS_PARTIAL;
$width = self::RATE + self::CAPACITY;
for ($i = 0; $i < $rounds; $i++) {
$row = array();
for ($j = 0; $j < $width; $j++) {
$row[] = self::roundConstant('Hades', $width * $i + $j);
}
self::$roundConstants[] = $row;
}
}
return self::$roundConstants;
}

private static function mds()
{
if (self::$mds === null) {
self::$mds = array(
array(self::field(3), self::field(1), self::field(1)),
array(self::field(1), self::field(-1), self::field(1)),
array(self::field(1), self::field(1), self::field(-2)),
);
}
return self::$mds;
}

private static function sbox($value)
{
$cube = $value->multiply($value)->multiply($value);
return self::field($cube);
}

private static function poseidonRound($values, $isFull, $index)
{
$roundConstants = self::roundConstants();
for ($i = 0; $i < count($values); $i++) {
$values[$i] = self::field($values[$i]->add($roundConstants[$index][$i]));
}
if ($isFull) {
for ($i = 0; $i < count($values); $i++) {
$values[$i] = self::sbox($values[$i]);
}
} else {
$last = count($values) - 1;
$values[$last] = self::sbox($values[$last]);
}
$result = array();
$mds = self::mds();
foreach ($mds as $row) {
$acc = Constants::ZERO();
for ($i = 0; $i < count($values); $i++) {
$product = $row[$i]->multiply($values[$i]);
$acc = $acc->add($product);
}
$result[] = self::field($acc);
}
return $result;
}

public static function poseidonHash($values)
{
$width = self::RATE + self::CAPACITY;
if (count($values) !== $width) {
throw new \InvalidArgumentException('Poseidon: wrong values length');
}
for ($i = 0; $i < count($values); $i++) {
$values[$i] = self::field($values[$i]);
}
$roundIndex = 0;
$halfRoundsFull = intval(self::ROUNDS_FULL / 2);
for ($i = 0; $i < $halfRoundsFull; $i++) {
$values = self::poseidonRound($values, true, $roundIndex);
$roundIndex++;
}
for ($i = 0; $i < self::ROUNDS_PARTIAL; $i++) {
$values = self::poseidonRound($values, false, $roundIndex);
$roundIndex++;
}
for ($i = 0; $i < $halfRoundsFull; $i++) {
$values = self::poseidonRound($values, true, $roundIndex);
$roundIndex++;
}
return $values;
}

public static function hashMany($values)
{
$padded = array_values($values);
$padded[] = 1;
while ((count($padded) % self::RATE) !== 0) {
$padded[] = 0;
}

$state = array(
Constants::ZERO(),
Constants::ZERO(),
Constants::ZERO()
);

for ($i = 0; $i < count($padded); $i += self::RATE) {
for ($j = 0; $j < self::RATE; $j++) {
$state[$j] = $state[$j]->add(Utils::toBn($padded[$i + $j]));
}
$state = self::poseidonHash($state);
}
return $state[0];
}

public static function computeHashOnElements($data)
{
return '0x' . Utils::removeLeadingZero(Poseidon::hashMany($data)->toHex());
}
}
19 changes: 0 additions & 19 deletions test/unit/PedersenHashTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,6 @@
*/
class PedersenHashTest extends TestCase
{
/**
* $fixture
*
* @var array
*/
// protected $fixture;

/**
* setUp
*
* @return void
*/
// public function setUp(): void
// {
// parent::setUp();
// $f = file_get_contents( dirname(__DIR__) . '/fixtures/issue2.json');
// $this->fixture = json_decode($f);
// }

/**
* testHash
*
Expand Down
29 changes: 29 additions & 0 deletions test/unit/PoseidonHashTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Test\Unit;

use InvalidArgumentException;
use stdClass;
use Test\TestCase;
use StarkNet\Crypto\PoseidonHash;

/**
* TODO: more test for poseidon hash
*/
class PoseidonHashTest extends TestCase
{
/**
* testHash
* @see https://github.com/paulmillr/noble-curves/blob/main/test/poseidon.test.ts
* @return void
*/
public function testPoseidonHash()
{
$result = PoseidonHash::poseidonHash([
'4379311784651118086770398084575492314150568148003994287303975907890254409956',
'5329163686893598957822497554130545759427567507701132391649270915797304266381',
2
]);
$this->assertEquals('2457757238178986673695038558497063891521456354791980183317105434323761563347', $result[0]->toString());
}
}
Loading