Skip to content

Commit

Permalink
added rsassapss
Browse files Browse the repository at this point in the history
  • Loading branch information
Arvolear committed Nov 12, 2024
1 parent 05e8492 commit b4e4ded
Show file tree
Hide file tree
Showing 3 changed files with 287 additions and 0 deletions.
224 changes: 224 additions & 0 deletions contracts/libs/crypto/RSASSAPSS.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/**
* @notice Cryptography module
*
* This library provides functionality to verify RSASSA-PSS signatures with MGF1 mask generation function.
*
* Users may provide custom hash functions via `Parameters` struct. However, the usage of `sha256` is recommended.
*
* Learn more about the algorithm [here](https://datatracker.ietf.org/doc/html/rfc3447#section-8.1).
*/
library RSASSAPSS {
/**
* @notice The RSASSA-PSS parameters.
* @param hashLength the hash function output length in bytes.
* @param saltLength the pss encoding salt length in bytes.
* @param hasher the function-pointer to a custom hash function.
*/
struct Parameters {
uint256 hashLength;
uint256 saltLength;
function(bytes memory) internal pure returns (bytes memory) hasher;
}

/**
* @notice Same as `verify` but with `sha256` hash function preconfiguration.
*/
function verifySha256(
bytes memory message_,
bytes memory s_,
bytes memory e_,
bytes memory n_
) internal view returns (bool) {
unchecked {
Parameters memory params_ = Parameters({
hashLength: 32,
saltLength: 32,
hasher: _sha256
});

return verify(params_, message_, s_, e_, n_);
}
}

/**
* @notice Verifies RSAPSS-SSA signature with custom parameters.
* @param params_ The parameters to specify the hash length, salt length, and hash function of choice.
* @param message_ The arbitrary message to be verified.
* @param s_ The "encrypted" signature
* @param e_ The public key exponent. `65537` is a recommended value.
* @param n_ The modulus of a public key.
*/
function verify(
Parameters memory params_,
bytes memory message_,
bytes memory s_,
bytes memory e_,
bytes memory n_
) internal view returns (bool) {
unchecked {
if (s_.length == 0 || e_.length == 0 || n_.length == 0) {
return false;
}

bytes memory decipher_ = _rsa(s_, e_, n_);

return _pss(message_, decipher_, params_);
}
}

/**
* @notice Calculates RSA via modexp (0x05) precompile.
*/
function _rsa(
bytes memory s_,
bytes memory e_,
bytes memory n_
) internal view returns (bytes memory decipher_) {
unchecked {
bytes memory input_ = abi.encodePacked(s_.length, e_.length, n_.length, s_, e_, n_);

decipher_ = new bytes(n_.length);

assembly {
pop(
staticcall(
sub(gas(), 2000), // gas buffer
5,
add(input_, 0x20),
mload(input_),
add(decipher_, 0x20),
mload(n_)
)
)
}
}
}

/**
* @notice Checks the PSS encoding.
*/
function _pss(
bytes memory message_,
bytes memory signature_,
Parameters memory params_
) private pure returns (bool) {
unchecked {
uint256 hashLength_ = params_.hashLength;
uint256 saltLength_ = params_.saltLength;
uint256 sigBytes_ = signature_.length;
uint256 sigBits_ = (sigBytes_ * 8 - 1) & 7;

if (message_.length > 2 ** 61 - 1) {
return false;
}

bytes memory messageHash_ = params_.hasher(message_);

if (sigBytes_ < hashLength_ + saltLength_ + 2) {
return false;
}

if (signature_[sigBytes_ - 1] != hex"BC") {
return false;
}

bytes memory db_ = new bytes(sigBytes_ - hashLength_ - 1);
bytes memory h_ = new bytes(hashLength_);

for (uint256 i = 0; i < db_.length; ++i) {
db_[i] = signature_[i];
}

for (uint256 i = 0; i < hashLength_; ++i) {
h_[i] = signature_[i + db_.length];
}

if (uint8(db_[0] & bytes1(uint8(((0xFF << (sigBits_)))))) == 1) {
return false;
}

bytes memory dbMask_ = _mgf(params_, h_, db_.length);

for (uint256 i = 0; i < db_.length; ++i) {
db_[i] ^= dbMask_[i];
}

if (sigBits_ > 0) {
db_[0] &= bytes1(uint8(0xFF >> (8 - sigBits_)));
}

uint256 zeroBytes_;

for (
zeroBytes_ = 0;
db_[zeroBytes_] == 0 && zeroBytes_ < (db_.length - 1);
++zeroBytes_
) {}

if (db_[zeroBytes_++] != hex"01") {
return false;
}

bytes memory salt_ = new bytes(saltLength_);

for (uint256 i = 0; i < salt_.length; ++i) {
salt_[i] = db_[db_.length - salt_.length + i];
}

bytes memory hh_ = params_.hasher(
abi.encodePacked(hex"0000000000000000", messageHash_, salt_)
);

/// check bytes equality
if (keccak256(h_) != keccak256(hh_)) {
return false;
}

return true;
}
}

/**
* @notice MGF1 mask generation function
*/
function _mgf(
Parameters memory params_,
bytes memory message_,
uint256 maskLen_
) private pure returns (bytes memory res_) {
unchecked {
uint256 hashLength_ = params_.hashLength;

bytes memory cnt_ = new bytes(4);

require(maskLen_ <= (2 ** 32) * hashLength_, "RSASSAPSS: mask too long");

for (uint256 i = 0; i < (maskLen_ + hashLength_ - 1) / hashLength_; ++i) {
cnt_[0] = bytes1(uint8((i >> 24) & 255));
cnt_[1] = bytes1(uint8((i >> 16) & 255));
cnt_[2] = bytes1(uint8((i >> 8) & 255));
cnt_[3] = bytes1(uint8(i & 255));

bytes memory hashedResInter_ = params_.hasher(abi.encodePacked(message_, cnt_));

res_ = abi.encodePacked(res_, hashedResInter_);
}

assembly {
mstore(res_, maskLen_)
}
}
}

/**
* @notice Utility `sha256` wrapper.
*/
function _sha256(bytes memory data) private pure returns (bytes memory) {
unchecked {
return abi.encodePacked(sha256(data));
}
}
}
17 changes: 17 additions & 0 deletions contracts/mock/libs/crypto/RSASSAPSSMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import {RSASSAPSS} from "../../../libs/crypto/RSASSAPSS.sol";

contract RSASSAPSSMock {
using RSASSAPSS for *;

function verifySha256(
bytes calldata message_,
bytes calldata s_,
bytes calldata e_,
bytes calldata n_
) external view returns (bool) {
return message_.verifySha256(s_, e_, n_);
}
}
46 changes: 46 additions & 0 deletions test/libs/crypto/RSASSAPSS.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ethers } from "hardhat";
import { expect } from "chai";
import { Reverter } from "@/test/helpers/reverter";

import { RSASSAPSSMock } from "@ethers-v6";

describe("RSASSAPSS", () => {
const reverter = new Reverter();

let rsassapss: RSASSAPSSMock;

before(async () => {
const RSASSAPSSMock = await ethers.getContractFactory("RSASSAPSSMock");

rsassapss = await RSASSAPSSMock.deploy();

await reverter.snapshot();
});

afterEach(reverter.revert);

describe("SHA256", () => {
it("should verify signature", async () => {
const message =
"0x308203c3a003020102020874442b6b708ef7a2304106092a864886f70d01010a3034a00f300d06096086480165030402010500a11c301a06092a864886f70d010108300d06096086480165030402010500a203020120302f310b3009060355040613025048310c300a060355040a0c034446413112301006035504030c09435343413031303037301e170d3232313231323136303030305a170d3333303430313135353935395a302d310b3009060355040613025048310c300a060355040a13034446413110300e060355040313074453303131313330820122300d06092a864886f70d01010105000382010f003082010a0282010100ab630b320a41ecf8886a904ab50fabcfad658f5af8a9f8aaecefe0dc5e2ea99eba3deccba3f58885f8574fe0ad5c889763afc2b68e66b5928403d508724ad1e7fd05c573c053e04660fd31128cff2e2f574ec92430202f5dafa6df66b46fb16ece1372424d3aa3b975428c59f18fe1f32e6c328b64f58f95e05684dfff2d21a85cb73bcb32ac172c8f782fa2ea942118379833bec37cab64de493ddae79014ed0e6fcaa2ca4cdc3bdb0442ba550cde8355194c3c3934b2d8bfa513fcf5788c0569e0527cd20daa5e8e114204661a3d1f21650d01703e7a112602cf8fbefbc329afc18d3d49a68b60e5c89c5152ad6e7f0480b0e4157b26640c569ae477e04f190203010001a38201c7308201c3300e0603551d0f0101ff040403020780301d0603551d0e04160414363257ff5b20debaa6e26c257d3ebb4fa1e7bcf5305e0603551d23045730558014a1436db84f1c134e49b387da56cee801102d4f73a133a431302f310b3009060355040613025048310c300a060355040a0c034446413112301006035504030c0943534341303130303782086b8a5f2f46fa934b302b0603551d1004243022800f32303232313231323136303030305a810f32303233303430313135353935395a30390603551d1104323030811c70617373706f72742e6469726563746f72406466612e676f762e7068a410300e310c300a06035504070c0350484c30390603551d1204323030811c70617373706f72742e6469726563746f72406466612e676f762e7068a410300e310c300a06035504070c0350484c306d0603551d1f046630643030a02ea02c862a68747470733a2f2f706b64646f776e6c6f6164312e6963616f2e696e742f43524c732f50484c2e63726c3030a02ea02c862a68747470733a2f2f706b64646f776e6c6f6164322e6963616f2e696e742f43524c732f50484c2e63726c3020060767810801010602041530131301501302504f130250441302505213025053";
const s =
"0x875ef62f6832599f41b50ca51a478c92ff47b61f2090157f64b425b1e1ad5612e6abb7d5808d9be5f0eaaa16d2b516ef161534c78d542ffd659107535c2bab643163fb9af27a50389792508d1cdbda347a103404c5e08d2d97c7935994631d42fe7e0caa892dc3ec39d3ac94dbccb3cd0870b21b9c836feed5bc32e9ec6830392bdade1fc9b5280fbaa2ceaa78d9524af3d015cbaf07eebc84a9caec81a4407452573a101b79772056193d207a8398690ed0dd0cc5a6410fd844d313c50934d6e1d556f8e7b39b12525f3cd766c9342fbd892e40408b0c232d888da11fc64d0f09db70971d395d7a1d2aacfe9da78e3c46ce43b3ce5b9fc1e6a90c065cdafa2e8a117d63c00cf9f54e3a3313789f03dd7efc76641c2cf5068ca4512c82fa6c62f6bd36b12523dc46f444b8312d2f6e6ec22cd10eddb19220d9b8ba4cc442dd836335482c6309d56e87492d2fdaefdb7b5ede566ed43eb87955451225846ce2535b803a9ca79034cc3aa41307cc57f0962cb8b2c3b99a5c87150387f7d8de6a18f6a838404c4aa5bb279378fb285c096d4c2664c700ac4e3c0cb44f920928e764dd4b10f22d3cb5bdfac78066b1b0a5ae75528e447b262510d41150a94ab0f645cc61ae99a3719bd29cf3901dde6de7cc162051f34c642a0f7854ef00d4143d755ad72bc71371c3a8dedb94118272f37f853bd171743b0c7a9a8cb96095476c9c";
const e = ethers.toBeHex(65537n);
const n =
"0xd24081be6cc14fe3fb4b35ab6df1a7f28f373017ef15a26b67ff2dd04773a3ef8942b7ba5f2f91aea469fe757e2e3362a907610b441f3610f528b1f39739a132c4bebc26c37d25b6d12481336fecddfc6bdcd011be4f2912ff0663cb70d9938280813dd3f32f2e6fef184881f784bbd2fd2d165b169d8594c45d832dbaebfdcb532d6542b57413825df5164b577dcdc248dfc4a8b071eb0bef021128e9172b77a18a5a6b00ebc0e07af0a9df6592684805a4ba0db00dddaabf793641ec0da51aecc6160acd56a0194d1161271b8feaaf0ae851ae65f1464c79607bbb237de3dbd0a299e9cda846c362976108d10555b57866a56c87c6a5d92d1888d6260fa90459afb14688b8a53921d2d477d1677518956412e01eb2592b27ad62a3d3a50777c4bee3f348b4788bb7bbd38d6bd902968a7b3640d75f98b78824ee9462e1b2d405b8c1ce7d7dbef479c2979553790b7d7fa8a6f05dad4cf95b92a218b410eb5b9df712d099b6952a07f122d21e95b934e9a765e758397b191fd01c0c4ae669039a0d7003308ab78d03809752bb7b676c3f3bbd9ac4b8cf0162efb50e01c52e3a97fed31d1e73f12b3da7b4df87fd7e93f70f92dc154d0bcef5a39c9631b2b50c7c7b91f4a63d4e3d487c37fc63bbdf87b71cad5581409661d15f77ff738a4d53d23424d999490607ebfd8293b2fb5c269cd8a19477ca88f6f7cb1abe00fb16c9";

expect(await rsassapss.verifySha256(message, s, e, n)).to.be.true;
});

it("should not verify invalid signature", async () => {
const message = "0x0123456789";
const s =
"0x875ef62f6832599f41b50ca51a478c92ff47b61f2090157f64b425b1e1ad5612e6abb7d5808d9be5f0eaaa16d2b516ef161534c78d542ffd659107535c2bab643163fb9af27a50389792508d1cdbda347a103404c5e08d2d97c7935994631d42fe7e0caa892dc3ec39d3ac94dbccb3cd0870b21b9c836feed5bc32e9ec6830392bdade1fc9b5280fbaa2ceaa78d9524af3d015cbaf07eebc84a9caec81a4407452573a101b79772056193d207a8398690ed0dd0cc5a6410fd844d313c50934d6e1d556f8e7b39b12525f3cd766c9342fbd892e40408b0c232d888da11fc64d0f09db70971d395d7a1d2aacfe9da78e3c46ce43b3ce5b9fc1e6a90c065cdafa2e8a117d63c00cf9f54e3a3313789f03dd7efc76641c2cf5068ca4512c82fa6c62f6bd36b12523dc46f444b8312d2f6e6ec22cd10eddb19220d9b8ba4cc442dd836335482c6309d56e87492d2fdaefdb7b5ede566ed43eb87955451225846ce2535b803a9ca79034cc3aa41307cc57f0962cb8b2c3b99a5c87150387f7d8de6a18f6a838404c4aa5bb279378fb285c096d4c2664c700ac4e3c0cb44f920928e764dd4b10f22d3cb5bdfac78066b1b0a5ae75528e447b262510d41150a94ab0f645cc61ae99a3719bd29cf3901dde6de7cc162051f34c642a0f7854ef00d4143d755ad72bc71371c3a8dedb94118272f37f853bd171743b0c7a9a8cb96095476c9c";
const e = ethers.toBeHex(65537n);
const n =
"0xd24081be6cc14fe3fb4b35ab6df1a7f28f373017ef15a26b67ff2dd04773a3ef8942b7ba5f2f91aea469fe757e2e3362a907610b441f3610f528b1f39739a132c4bebc26c37d25b6d12481336fecddfc6bdcd011be4f2912ff0663cb70d9938280813dd3f32f2e6fef184881f784bbd2fd2d165b169d8594c45d832dbaebfdcb532d6542b57413825df5164b577dcdc248dfc4a8b071eb0bef021128e9172b77a18a5a6b00ebc0e07af0a9df6592684805a4ba0db00dddaabf793641ec0da51aecc6160acd56a0194d1161271b8feaaf0ae851ae65f1464c79607bbb237de3dbd0a299e9cda846c362976108d10555b57866a56c87c6a5d92d1888d6260fa90459afb14688b8a53921d2d477d1677518956412e01eb2592b27ad62a3d3a50777c4bee3f348b4788bb7bbd38d6bd902968a7b3640d75f98b78824ee9462e1b2d405b8c1ce7d7dbef479c2979553790b7d7fa8a6f05dad4cf95b92a218b410eb5b9df712d099b6952a07f122d21e95b934e9a765e758397b191fd01c0c4ae669039a0d7003308ab78d03809752bb7b676c3f3bbd9ac4b8cf0162efb50e01c52e3a97fed31d1e73f12b3da7b4df87fd7e93f70f92dc154d0bcef5a39c9631b2b50c7c7b91f4a63d4e3d487c37fc63bbdf87b71cad5581409661d15f77ff738a4d53d23424d999490607ebfd8293b2fb5c269cd8a19477ca88f6f7cb1abe00fb16c9";

expect(await rsassapss.verifySha256(message, s, e, n)).to.be.false;
});
});
});

0 comments on commit b4e4ded

Please sign in to comment.