Skip to content

Commit

Permalink
contracts: implement SHA512-256 as it's used throughout the Oasis eco…
Browse files Browse the repository at this point in the history
…system
  • Loading branch information
CedarMist committed Oct 15, 2024
1 parent 5240107 commit ccbed9c
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 9 deletions.
70 changes: 70 additions & 0 deletions contracts/contracts/HMAC_sha512_256.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.0;

import {sha512_256} from "./Sapphire.sol";

// Note that the SHA512_256 block size is 128 bytes, while the output is 32 bytes
uint256 constant SHA512_256_BLOCK_SIZE = 128;

// We don't (yet) have the MCOPY opcode, so use the IDENTITY precompile
uint256 constant PRECOMPILE_IDENTITY_ADDRESS = 0x4;

// HMAC block-sized inner padding
bytes32 constant HMAC_IPAD = 0x3636363636363636363636363636363636363636363636363636363636363636;

// OPAD ^ IPAD, (OPAD = 0x5c)
bytes32 constant HMAC_OPAD_XOR_IPAD = 0x6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a;

/**
* @notice Implements HMAC using SHA512-256.
* @dev https://en.wikipedia.org/wiki/HMAC
* @param key the secret key.
* @param message the message to be authenticated.
*
* #### Example
*
* ```solidity
* bytes memory key = "arbitrary length key";
* bytes memory message = "arbitrary length message";
* bytes32 hmac = HMAC_sha512_256(key, message)
* ```
*/
function HMAC_sha512_256(bytes memory key, bytes memory message)
view
returns (bytes32)
{
bytes32[4] memory buf;

if (key.length > SHA512_256_BLOCK_SIZE) {
buf[0] = sha512_256(key);
} else {
bool success;

assembly {
let size := mload(key)
success := staticcall(
gas(),
PRECOMPILE_IDENTITY_ADDRESS,
add(32, key), // Skip uint256 length prefix of key bytes
size,
buf,
size
)
}

require(success, "memcpy");
}

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

bytes32 ihash = sha512_256(abi.encodePacked(buf, message));

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

return sha512_256(abi.encodePacked(buf, ihash));
}
9 changes: 9 additions & 0 deletions contracts/contracts/tests/HashTests.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity ^0.8.0;

import {sha512, sha512_256, sha384} from "../Sapphire.sol";
import {HMAC_sha512_256} from "../HMAC_sha512_256.sol";

contract HashTests {
function testSHA512(bytes memory data)
Expand All @@ -24,4 +25,12 @@ contract HashTests {
function testSHA512_256(bytes memory data) external view returns (bytes32) {
return sha512_256(data);
}

function testHMAC_SHA512_256(bytes memory key, bytes memory data)
external
view
returns (bytes32)
{
return HMAC_sha512_256(key, data);
}
}
28 changes: 19 additions & 9 deletions contracts/test/hashes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@ import { expect } from 'chai';
import { randomBytes, createHash } from 'crypto';
import { ethers } from 'hardhat';
import { HashTests } from '../typechain-types/contracts/tests/HashTests';
import { HashTests__factory } from '../typechain-types/factories/contracts/tests';
import { BytesLike, Overrides } from 'ethers';
import { BytesLike, hexlify, Overrides } from 'ethers';
import { sha512_256 } from '@noble/hashes/sha512';
import { hmac } from '@noble/hashes/hmac';

type HasherTestT = (
data: BytesLike,
overrides?: Overrides | undefined,
) => Promise<string>;
type HasherTestT = (data: BytesLike, overrides?: Overrides) => Promise<string>;

describe('Hashes', () => {
let contract: HashTests;

before(async () => {
const factory = (await ethers.getContractFactory(
'HashTests',
)) as HashTests__factory;
const factory = await ethers.getContractFactory('HashTests');
contract = await factory.deploy();
await contract.waitForDeployment();
});
Expand All @@ -41,4 +37,18 @@ describe('Hashes', () => {
it('SHA384', async () => {
await testHashes('SHA384', contract.testSHA384.bind(contract));
});

it('HMAC SHA512-256', async () => {
for (let i = 0; i < 1024; i = i + (1 + i / 5)) {
const key = randomBytes(i);
for (let j = 0; j < 1024; j = j + (1 + j / 5)) {
const msg = randomBytes(j);
const expected = new Uint8Array(
hmac.create(sha512_256, key).update(msg).digest().buffer,
);
const actual = await contract.testHMAC_SHA512_256(key, msg);
expect(hexlify(actual)).eq(hexlify(expected));
}
}
});
});

0 comments on commit ccbed9c

Please sign in to comment.