-
-
Notifications
You must be signed in to change notification settings - Fork 320
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Builtin SHA256 hashing #6977
base: main
Are you sure you want to change the base?
Builtin SHA256 hashing #6977
Changes from all commits
81ac895
48fffa0
fff57ae
1bde780
8168545
881b1be
cb294ee
07e0c28
883d580
0ab4f45
315c396
0824029
277bc0b
2f986f6
2ef3af3
44243ba
a37dc00
ebbc440
eae0c5e
61db153
302d5c3
43bfd8a
34fb4cb
75f3420
32fa02b
2acbbd2
5fe4d61
638d214
5e3f502
d920853
e54ff61
0f37191
6a07cb4
307d6a7
0028062
1cc7a8e
070a312
5e7cc8d
463d694
c034933
4035953
861c75f
608f787
056b918
cd51cdb
8c47be1
e614ffa
19ffe1a
60379ce
b8f429f
bbc7b58
40eb6fc
83ae431
f80f009
bed0497
cc9be6d
1f30fe0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
const std = @import("std"); | ||
const builtin = @import("builtin"); | ||
const crypto = std.crypto; | ||
const sha2 = crypto.hash.sha2; | ||
const list = @import("list.zig"); | ||
const utils = @import("utils.zig"); | ||
const testing = std.testing; | ||
|
||
const Sha256 = extern struct { | ||
location: *sha2.Sha256, | ||
}; | ||
|
||
fn create(comptime T: type) *T { | ||
//test_roc_alloc ignores alignment | ||
if (builtin.is_test) { | ||
return std.testing.allocator.create(T) catch unreachable; | ||
} | ||
return @alignCast(@ptrCast(utils.allocateWithRefcount(@sizeOf(sha2.Sha256), @alignOf(sha2.Sha256), false))); | ||
} | ||
|
||
pub fn emptySha256() callconv(.C) Sha256 { | ||
const location: *sha2.Sha256 = create(sha2.Sha256); | ||
location.* = sha2.Sha256.init(.{}); | ||
return Sha256{ | ||
.location = location, | ||
}; | ||
} | ||
|
||
test "emptySha256" { | ||
const empty_sha = emptySha256(); | ||
defer std.testing.allocator.destroy(empty_sha.location); | ||
const empty_hash = empty_sha.location.*.peek(); | ||
try std.testing.expect(sameBytesAsHex("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", empty_hash[0..empty_hash.len])); | ||
} | ||
|
||
pub fn sha256AddBytes(sha: Sha256, data: list.RocList) callconv(.C) Sha256 { | ||
const out = emptySha256(); | ||
out.location.* = sha.location.*; | ||
if (data.bytes) |bytes| { | ||
const byteSlice: []u8 = bytes[0..data.length]; | ||
out.location.*.update(byteSlice); | ||
} | ||
return out; | ||
} | ||
|
||
test "sha256AddBytes" { | ||
const empty_sha = emptySha256(); | ||
defer std.testing.allocator.destroy(empty_sha.location); | ||
const abc = list.RocList.fromSlice(u8, "abc", false); | ||
defer abc.decref(@alignOf(u8), @sizeOf(u8), false, rcNone); | ||
const abc_sha = sha256AddBytes(empty_sha, abc); | ||
defer std.testing.allocator.destroy(abc_sha.location); | ||
const abc_hash = abc_sha.location.*.peek(); | ||
try std.testing.expect(sameBytesAsHex("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", abc_hash[0..abc_hash.len])); | ||
} | ||
|
||
pub const Digest256 = extern struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently a struct with as few fundamental values in it as possible, because that is more convenient on the Roc side, and it is maybe clearest to keep them the same. I can see a case for |
||
first_half: u128, | ||
second_half: u128, | ||
}; | ||
|
||
pub fn sha256Digest(sha: Sha256) callconv(.C) Digest256 { | ||
return @bitCast(sha.location.*.peek()); | ||
} | ||
|
||
test "sha256Digest" { | ||
const empty_sha = emptySha256(); | ||
defer std.testing.allocator.destroy(empty_sha.location); | ||
const digest = sha256Digest(empty_sha); | ||
const first_half_bytes: [16]u8 = @bitCast(digest.first_half); | ||
const second_half_bytes: [16]u8 = @bitCast(digest.second_half); | ||
try std.testing.expect(sameBytesAsHex("e3b0c44298fc1c149afbf4c8996fb924", first_half_bytes[0..first_half_bytes.len])); | ||
try std.testing.expect(sameBytesAsHex("27ae41e4649b934ca495991b7852b855", second_half_bytes[0..second_half_bytes.len])); | ||
} | ||
//----------------test utilities ------------------------ | ||
fn rcNone(_: ?[*]u8) callconv(.C) void {} | ||
|
||
fn sameBytesAsHex(comptime expected_hex: [:0]const u8, input: []const u8) bool { | ||
if (expected_hex.len != 2 * input.len) { | ||
return false; | ||
} | ||
|
||
for (input, 0..) |input_byte, i| { | ||
const hex_byte = std.fmt.parseInt(u8, expected_hex[2 * i .. 2 * i + 2], 16) catch unreachable; | ||
if (hex_byte != input_byte) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
module [ | ||
emptySha256, | ||
sha256AddBytes, | ||
sha256Digest, | ||
hashSha256, | ||
digest256ToBytes, | ||
Sha256, | ||
Digest256, | ||
] | ||
|
||
import Bool exposing [Eq] | ||
import List | ||
import Num exposing [U8, U64, U128] | ||
import Result | ||
import Str | ||
|
||
## Represents the state of a SHA-256 cryptographic hashing function, after some (or no) data has been added to the hash. | ||
Sha256 := { location : U64 } | ||
|
||
## Represents the digest of some data produced by the SHA-256 cryptographic hashing function as an opaque type. | ||
|
||
## `Digest256` implements the `Eq` ability. | ||
Digest256 := { firstHalf : U128, secondHalf : U128 } implements [Eq] | ||
|
||
## Returns an empty SHA-256 hasher. | ||
emptySha256 : {} -> Sha256 | ||
|
||
## Adds bytes of data to be hashed by a SHA-256 hasher.. | ||
sha256AddBytes : Sha256, List U8 -> Sha256 | ||
|
||
## Returns the digest of the cryptographic hashing function represented by a SHA-256 hasher.. | ||
sha256Digest : Sha256 -> Digest256 | ||
|
||
## Applies the SHA-256 cryptographic hashing function to some bytes. | ||
hashSha256 : List U8 -> Digest256 | ||
hashSha256 = \bytes -> emptySha256 {} |> sha256AddBytes bytes |> sha256Digest | ||
|
||
# Assumes little-endian. Probably shouldn't. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume roc should be able to compile to big-endian architectures, too, Since, I can't see anyway of getting this information in roc-programmer space, I think some of this functionality will need pushing to zig |
||
u128Bytes : U128 -> List U8 | ||
u128Bytes = \number -> | ||
loop = \n, bytes, place -> | ||
if place == 16 then | ||
bytes | ||
else | ||
newByte = n |> Num.bitwiseAnd 255 |> Num.toU8 | ||
loop (Num.shiftRightBy n 8) (List.append bytes newByte) (place + 1) | ||
loop number [] 0 | ||
|
||
expect | ||
bytes1 = u128Bytes 1 | ||
bytes1 == [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] | ||
|
||
expect | ||
bytes257 = u128Bytes 0x000102030405060708090a0b0c0d0e0f | ||
bytes257 == [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0] | ||
|
||
## Returns the bytes of a SHA-256 digest as a list. | ||
digest256ToBytes : Digest256 -> List U8 | ||
digest256ToBytes = \@Digest256 { firstHalf, secondHalf } -> | ||
List.concat (u128Bytes firstHalf) (u128Bytes secondHalf) | ||
|
||
# test data taken from https://ziglang.org/documentation/0.11.0/std/src/std/crypto/sha2.zig.html#L434 | ||
digestBytesOfEmpty : List U8 | ||
digestBytesOfEmpty = fromHexString "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" | ||
|
||
digestBytesOfAbc : List U8 | ||
digestBytesOfAbc = fromHexString "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" | ||
|
||
digestBytesOfLong : List U8 | ||
digestBytesOfLong = fromHexString "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1" | ||
|
||
expect | ||
data : List U8 | ||
data = [] | ||
want = digestBytesOfEmpty | ||
got = data |> hashSha256 |> digest256ToBytes | ||
want == got | ||
|
||
expect | ||
data = ['a', 'b', 'c'] | ||
want = digestBytesOfAbc | ||
got = data |> hashSha256 |> digest256ToBytes | ||
want == got | ||
|
||
expect | ||
data = Str.toUtf8 "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu" | ||
want = digestBytesOfLong | ||
got = data |> hashSha256 |> digest256ToBytes | ||
want == got | ||
|
||
expect | ||
want = digestBytesOfEmpty | ||
got = emptySha256 {} |> sha256Digest |> digest256ToBytes | ||
want == got | ||
|
||
expect | ||
data = ['a', 'b', 'c'] | ||
want = digestBytesOfAbc | ||
got = | ||
emptySha256 {} | ||
|> sha256AddBytes data | ||
|> sha256Digest | ||
|> digest256ToBytes | ||
want == got | ||
|
||
expect | ||
want = digestBytesOfAbc | ||
got = | ||
emptySha256 {} | ||
|> sha256AddBytes ['a'] | ||
|> sha256AddBytes ['b'] | ||
|> sha256AddBytes ['c'] | ||
|> sha256Digest | ||
|> digest256ToBytes | ||
want == got | ||
|
||
fromHexString : Str -> List U8 | ||
fromHexString = \hex -> | ||
fromHexDigit = \smallNumber -> | ||
if smallNumber <= '9' then | ||
smallNumber - '0' | ||
else | ||
smallNumber - 'a' + 10 | ||
|
||
fromHexDigits = \pair -> | ||
first = pair |> List.first |> Result.withDefault 0 | ||
second = pair |> List.get 1 |> Result.withDefault 0 | ||
16 * (fromHexDigit first) + (fromHexDigit second) | ||
|
||
hex | ||
|> Str.toUtf8 | ||
|> List.chunksOf 2 | ||
|> List.map fromHexDigits |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,4 +12,5 @@ package [ | |
Box, | ||
Inspect, | ||
Task, | ||
Crypto, | ||
] {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1431,6 +1431,33 @@ pub(crate) fn run_low_level<'a, 'ctx>( | |
|
||
call_bitcode_fn(env, &[], bitcode::UTILS_DICT_PSEUDO_SEED) | ||
} | ||
CryptoEmptySha256 => call_bitcode_fn(env, &[], bitcode::CRYPTO_EMPTY_SHA256), | ||
CryptoSha256AddBytes => { | ||
// Crypto.sha256AddBytes : Sha256, List U8 -> Sha256 | ||
arguments!(sha, data); | ||
|
||
let list_ptr = create_entry_block_alloca(env, data.get_type(), "list_alloca"); | ||
env.builder.new_build_store(list_ptr, data); | ||
|
||
call_bitcode_fn( | ||
env, | ||
&[sha, list_ptr.into()], | ||
bitcode::CRYPTO_SHA256_ADD_BYTES, | ||
) | ||
} | ||
CryptoSha256Digest => { | ||
// Crypto.sha256Digest : Sha256 -> Digest256 | ||
arguments!(sha); | ||
|
||
call_bitcode_fn_fixing_for_convention( | ||
env, | ||
layout_interner, | ||
env.module.get_struct_type("crypto.Digest256").unwrap(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh wait, it is this |
||
&[sha], | ||
layout, | ||
bitcode::CRYPTO_SHA256_DIGEST, | ||
) | ||
} | ||
|
||
ListIncref | ListDecref | SetJmp | LongJmp | SetLongJmpBuffer => { | ||
unreachable!("only inserted in dev backend codegen") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A pointer because: