feat(stdlib): Crypto.* — SHA-256 + HMAC-SHA256, constant-time verify (#19)
All checks were successful
docs / build-and-deploy (push) Successful in 3s
All checks were successful
docs / build-and-deploy (push) Successful in 3s
The security-sensitive counterpart to the fast, non-cryptographic Hash.* library: standard, test-vector-backed hashing for signed saves and message integrity, kept in its own namespace so nobody reaches for the wrong tool. Crypto.sha256(s) SHA-256 -> 64-char lowercase hex Crypto.hmac_sha256(key, msg) HMAC-SHA256 -> 64-char hex Crypto.verify_hmac(key, msg, mac) recompute + constant-time compare -> bool Crypto.hex(s) lowercase hex of a string's bytes Crypto.ct_equal(a, b) constant-time string equality The primitives are implemented from scratch in plain integer LLVM IR (FIPS 180-4 / RFC 2104): no libc crypto, no data-dependent branches in the compression rounds, so a given input hashes to the same 32 bytes on every platform and run. Digests are returned as hex strings, not raw bytes, because a `str` is null-terminated and a raw digest can contain a NUL. MAC checks use a non-short-circuiting compare so timing does not leak how much of a forged tag was correct. Emitted on demand via g_uses_cryptort, mirroring the emit_hash prelude gate. Scoped to the deterministic, known-answer-testable core; OS-backed random_bytes (the one piece that can't be validated by test vectors) is left for a follow-up. Tested against published SHA-256 vectors (empty/"abc"/fox + 55/56/64-byte multi-block padding) and HMAC-SHA256 vectors; wired into the self-host suite as `crypto`. Docs: a new Crypto section with honest "what this protects / does not" guidance, one page per method, all fences checked and in the inventory. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
1858ab65ad
commit
9ae69e64b4
15 changed files with 11536 additions and 10437 deletions
11
docs/language/crypto/_section.md
Normal file
11
docs/language/crypto/_section.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
id: crypto
|
||||||
|
title: Crypto
|
||||||
|
order: 6
|
||||||
|
---
|
||||||
|
|
||||||
|
Secure, test-vector-backed hashing for the handful of security-sensitive things a game actually does: signing a save or leaderboard payload so casual tampering is detectable, and verifying that a network message or token was not forged by someone who does not hold the key. This is the deliberate counterpart to the fast <a href="ns-Hash"><code>Hash</code></a> library — same idea, opposite trade-off. <code>Hash</code> is fast and reversible and must never guard anything; <code>Crypto</code> is <a href="crypto-sha256"><code>SHA-256</code></a> and <a href="crypto-hmac_sha256"><code>HMAC-SHA256</code></a> implemented to the standard, so the algorithms are the ones with published known-answer tests rather than anything home-grown.
|
||||||
|
|
||||||
|
Digests are returned as lowercase hex strings, not raw bytes — a <code>str</code> is null-terminated and a raw digest can contain a zero byte, so hex is the form you can print, store, and compare directly.
|
||||||
|
|
||||||
|
What this is not: it is not DRM and it is not unbeatable anti-cheat. A client-side game cannot keep a secret from the machine it runs on — a determined owner can always read the key out of the binary. Use it to make *casual* tampering detectable and to authenticate messages between parties who share a key. To verify a MAC always use <a href="crypto-verify_hmac"><code>Crypto.verify_hmac</code></a> (a constant-time check), never <code>==</code>, which leaks how much of a guessed MAC was correct.
|
||||||
27
docs/language/crypto/crypto-ct_equal.md
Normal file
27
docs/language/crypto/crypto-ct_equal.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
id: crypto-ct_equal
|
||||||
|
name: Crypto.ct_equal
|
||||||
|
category: crypto
|
||||||
|
kind: namespace-method
|
||||||
|
tokens: Crypto.ct_equal
|
||||||
|
sig: Crypto.ct_equal(a, b) -> bool
|
||||||
|
tip: Constant-time string equality for secrets.
|
||||||
|
order: 5
|
||||||
|
ns: Crypto
|
||||||
|
member: ct_equal
|
||||||
|
---
|
||||||
|
|
||||||
|
Compares two strings for equality without short-circuiting: every character is examined even once a difference is found, so the time taken does not reveal where — or whether — the strings first diverged. Reach for it whenever you compare a secret, token, or MAC that an attacker might be probing. For the common case of checking a message tag, <a href="crypto-verify_hmac"><code>Crypto.verify_hmac</code></a> already does this for you; use <code>ct_equal</code> directly when you hold both values yourself. Strings of different length return <code>false</code> at once (length is not secret). For ordinary, non-secret text just use <code>==</code> — the constant-time guarantee is not free.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- `a` — one string
|
||||||
|
- `b` — the other string
|
||||||
|
|
||||||
|
```ludic
|
||||||
|
program CompareToken {
|
||||||
|
entry {
|
||||||
|
if Crypto.ct_equal("abc", "abc") { print(1) } else { print(0) } # 1
|
||||||
|
if Crypto.ct_equal("abc", "abd") { print(1) } else { print(0) } # 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
25
docs/language/crypto/crypto-hex.md
Normal file
25
docs/language/crypto/crypto-hex.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
id: crypto-hex
|
||||||
|
name: Crypto.hex
|
||||||
|
category: crypto
|
||||||
|
kind: namespace-method
|
||||||
|
tokens: Crypto.hex
|
||||||
|
sig: Crypto.hex(s) -> string
|
||||||
|
tip: Lowercase hex of a string's bytes.
|
||||||
|
order: 4
|
||||||
|
ns: Crypto
|
||||||
|
member: hex
|
||||||
|
---
|
||||||
|
|
||||||
|
Encodes the bytes of <code>s</code> as a lowercase hex string — two characters per byte, so an <em>n</em>-byte input becomes a <em>2n</em>-character result. It is the same encoding <a href="crypto-sha256"><code>Crypto.sha256</code></a> already applies to a digest, exposed on its own so you can render arbitrary bytes (a key id, a small binary token) in a form that is safe to print, log, or embed in text.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- `s` — the string whose bytes are encoded
|
||||||
|
|
||||||
|
```ludic
|
||||||
|
program HexDump {
|
||||||
|
entry {
|
||||||
|
print(Crypto.hex("abc")) # 616263
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
29
docs/language/crypto/crypto-hmac_sha256.md
Normal file
29
docs/language/crypto/crypto-hmac_sha256.md
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
id: crypto-hmac_sha256
|
||||||
|
name: Crypto.hmac_sha256
|
||||||
|
category: crypto
|
||||||
|
kind: namespace-method
|
||||||
|
tokens: Crypto.hmac_sha256
|
||||||
|
sig: Crypto.hmac_sha256(key, msg) -> string
|
||||||
|
tip: Sign a message with a shared secret key.
|
||||||
|
order: 2
|
||||||
|
ns: Crypto
|
||||||
|
member: hmac_sha256
|
||||||
|
---
|
||||||
|
|
||||||
|
Computes HMAC-SHA256 over <code>msg</code> under the secret <code>key</code> (RFC 2104) and returns the 64-character lowercase hex tag. Unlike a bare hash, a MAC cannot be recomputed without the key, so it authenticates the message: attach the tag to a save file or a network packet, and a receiver who shares the key can tell whether the payload was altered or forged. To check the tag on the other side, pass it to <a href="crypto-verify_hmac"><code>Crypto.verify_hmac</code></a> rather than comparing hex with <code>==</code>.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- `key` — the shared secret; keep it out of the shipped client where you can
|
||||||
|
- `msg` — the payload being signed
|
||||||
|
|
||||||
|
```ludic
|
||||||
|
program SignSave {
|
||||||
|
entry {
|
||||||
|
let key = "s3cret"
|
||||||
|
let payload = "score=9001;level=12"
|
||||||
|
let mac = Crypto.hmac_sha256(key, payload)
|
||||||
|
print(mac)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
26
docs/language/crypto/crypto-sha256.md
Normal file
26
docs/language/crypto/crypto-sha256.md
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
id: crypto-sha256
|
||||||
|
name: Crypto.sha256
|
||||||
|
category: crypto
|
||||||
|
kind: namespace-method
|
||||||
|
tokens: Crypto.sha256
|
||||||
|
sig: Crypto.sha256(s) -> string
|
||||||
|
tip: SHA-256 of a string, as 64 hex characters.
|
||||||
|
order: 1
|
||||||
|
ns: Crypto
|
||||||
|
member: sha256
|
||||||
|
---
|
||||||
|
|
||||||
|
Computes the SHA-256 digest of the bytes of <code>s</code> and returns it as a 64-character lowercase hex string. This is the standard, FIPS 180-4 algorithm — the same digest every other conforming implementation produces — so it is deterministic across platforms and runs and is backed by published known-answer vectors. Use it to fingerprint content, or as the building block under <a href="crypto-hmac_sha256"><code>Crypto.hmac_sha256</code></a> for signing. On its own SHA-256 is <em>not</em> a message authentication code: anyone can recompute it, so it proves what the data is, not who produced it.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- `s` — the string whose bytes are hashed
|
||||||
|
|
||||||
|
```ludic
|
||||||
|
program Fingerprint {
|
||||||
|
entry {
|
||||||
|
let digest = Crypto.sha256("abc")
|
||||||
|
print(digest) # ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
34
docs/language/crypto/crypto-verify_hmac.md
Normal file
34
docs/language/crypto/crypto-verify_hmac.md
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
id: crypto-verify_hmac
|
||||||
|
name: Crypto.verify_hmac
|
||||||
|
category: crypto
|
||||||
|
kind: namespace-method
|
||||||
|
tokens: Crypto.verify_hmac
|
||||||
|
sig: Crypto.verify_hmac(key, msg, mac) -> bool
|
||||||
|
tip: Constant-time check that a MAC matches.
|
||||||
|
order: 3
|
||||||
|
ns: Crypto
|
||||||
|
member: verify_hmac
|
||||||
|
---
|
||||||
|
|
||||||
|
Recomputes HMAC-SHA256(<code>key</code>, <code>msg</code>) and compares it to the supplied <code>mac</code> hex string, returning <code>true</code> only if they match. The comparison is <em>constant-time</em>: it never stops early on the first differing character, so it does not leak — through how long the check took — how many leading bytes of a forged tag happened to be right. That leak is exactly what lets an attacker guess a MAC one byte at a time, which is why you should always verify with this and never with <code>==</code>. A mismatched length returns <code>false</code> immediately (the length of a MAC is not a secret).
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- `key` — the shared secret used to sign
|
||||||
|
- `msg` — the payload as received
|
||||||
|
- `mac` — the hex tag to check, e.g. from <a href="crypto-hmac_sha256"><code>Crypto.hmac_sha256</code></a>
|
||||||
|
|
||||||
|
```ludic
|
||||||
|
program CheckSave {
|
||||||
|
entry {
|
||||||
|
let key = "s3cret"
|
||||||
|
let payload = "score=9001;level=12"
|
||||||
|
let mac = Crypto.hmac_sha256(key, payload)
|
||||||
|
if Crypto.verify_hmac(key, payload, mac) {
|
||||||
|
print(1) # untampered
|
||||||
|
} else {
|
||||||
|
print(0) # altered or forged
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -25,6 +25,7 @@ var g_uses_mathrt: bool = false # Math.sqrt/sin/cos/tan was emitted -> emit the
|
||||||
var g_uses_textrt: bool = false # Text.upper/lower/trim/repeat/pad was emitted -> emit the text builders
|
var g_uses_textrt: bool = false # Text.upper/lower/trim/repeat/pad was emitted -> emit the text builders
|
||||||
var g_uses_textrt2: bool = false # Text.split/join/replace was emitted -> emit the string/slice builders
|
var g_uses_textrt2: bool = false # Text.split/join/replace was emitted -> emit the string/slice builders
|
||||||
var g_uses_hashrt: bool = false # Hash.of/fnv1a/crc32 was emitted -> emit the byte-stream hashers
|
var g_uses_hashrt: bool = false # Hash.of/fnv1a/crc32 was emitted -> emit the byte-stream hashers
|
||||||
|
var g_uses_cryptort: bool = false # Crypto.* was emitted -> emit the SHA-256 / HMAC runtime
|
||||||
var g_uses_datert: bool = false # Date.*/DateTime.* was emitted -> emit the civil<->epoch conversions
|
var g_uses_datert: bool = false # Date.*/DateTime.* was emitted -> emit the civil<->epoch conversions
|
||||||
var g_uses_longstr: bool = false # string(long) / interpolating a long was emitted -> emit fn_long_str
|
var g_uses_longstr: bool = false # string(long) / interpolating a long was emitted -> emit fn_long_str
|
||||||
|
|
||||||
|
|
|
||||||
256
selfhost/emit_crypto.ludic
Normal file
256
selfhost/emit_crypto.ludic
Normal file
|
|
@ -0,0 +1,256 @@
|
||||||
|
# emit_crypto.ludic — the Crypto.* namespace: secure, test-vector-backed hashing
|
||||||
|
# for the few security-sensitive things games do (signed saves, message/token
|
||||||
|
# integrity), kept deliberately separate from the fast, non-cryptographic Hash.*
|
||||||
|
# library so nobody reaches for the wrong tool.
|
||||||
|
#
|
||||||
|
# Crypto.sha256(s) SHA-256 of the bytes of `s` -> 64-char lowercase hex
|
||||||
|
# Crypto.hmac_sha256(key, msg) HMAC-SHA256(key, msg) -> 64-char lowercase hex
|
||||||
|
# Crypto.verify_hmac(key, msg, mac) recompute the MAC and compare it to `mac`
|
||||||
|
# in constant time -> bool (the tamper check)
|
||||||
|
# Crypto.hex(s) lowercase hex of the bytes of `s`
|
||||||
|
# Crypto.ct_equal(a, b) constant-time string equality (for secrets/MACs)
|
||||||
|
#
|
||||||
|
# This is a well-specified standard algorithm (FIPS 180-4 / RFC 2104), implemented
|
||||||
|
# from scratch in plain integer IR: no libc crypto, no allocation-order or
|
||||||
|
# data-dependent branches in the compression rounds, so a given input hashes to
|
||||||
|
# the same 32 bytes on every platform and every run. Digests are returned as hex
|
||||||
|
# strings (not raw bytes) because a `str` is null-terminated and a raw digest can
|
||||||
|
# contain NUL — hex is the directly-printable, directly-comparable form.
|
||||||
|
#
|
||||||
|
# What this is NOT: it is not DRM and not unbeatable anti-cheat. A client-side
|
||||||
|
# game cannot keep a secret from the machine running it; a determined owner can
|
||||||
|
# always read the key out of the binary. Use it to make *casual* tampering with a
|
||||||
|
# save or a leaderboard payload detectable, and to verify a network message was
|
||||||
|
# not forged by a third party who does not hold the key — nothing stronger.
|
||||||
|
|
||||||
|
function is_crypto_ns(meth: pointer) -> bool {
|
||||||
|
if (meth == "sha256") or (meth == "hmac_sha256") or (meth == "verify_hmac") { return true }
|
||||||
|
if (meth == "hex") or (meth == "ct_equal") { return true }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function emit_crypto_ns(meth: pointer, e: Node) -> Val {
|
||||||
|
g_uses_cryptort = true
|
||||||
|
if (meth == "sha256") { # SHA-256 -> 64-char hex string
|
||||||
|
let s = emit_expr(e.kids[0])
|
||||||
|
return val(emit_bind(`call ptr @fn_sha256_hex(ptr {s.code})`), "string")
|
||||||
|
}
|
||||||
|
if (meth == "hmac_sha256") { # HMAC-SHA256 -> 64-char hex string
|
||||||
|
let k = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1])
|
||||||
|
return val(emit_bind(`call ptr @fn_hmac_sha256_hex(ptr {k.code}, ptr {m.code})`), "string")
|
||||||
|
}
|
||||||
|
if (meth == "hex") { # lowercase hex of a string's bytes
|
||||||
|
let s = emit_expr(e.kids[0])
|
||||||
|
return val(emit_bind(`call ptr @fn_str_hex(ptr {s.code})`), "string")
|
||||||
|
}
|
||||||
|
if (meth == "ct_equal") { # constant-time string equality -> bool
|
||||||
|
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||||
|
return val(emit_bind(`call i32 @fn_ct_streq(ptr {a.code}, ptr {b.code})`), "bool")
|
||||||
|
}
|
||||||
|
# verify_hmac(key, msg, mac): recompute HMAC-SHA256(key, msg) and compare it to
|
||||||
|
# the supplied hex `mac` in constant time. This is the safe way to check a MAC —
|
||||||
|
# `==` would leak, byte by byte, how much of a forged MAC was correct.
|
||||||
|
let k = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1]); let mac = emit_expr(e.kids[2])
|
||||||
|
let computed = emit_bind(`call ptr @fn_hmac_sha256_hex(ptr {k.code}, ptr {m.code})`)
|
||||||
|
return val(emit_bind(`call i32 @fn_ct_streq(ptr {computed}, ptr {mac.code})`), "bool")
|
||||||
|
}
|
||||||
|
|
||||||
|
# emit_crypto_prelude — the SHA-256 / HMAC-SHA256 runtime, emitted once per program
|
||||||
|
# that uses Crypto.* (g_uses_cryptort). Everything below is FIPS 180-4 / RFC 2104
|
||||||
|
# to the letter, in pure integer IR with no libc crypto.
|
||||||
|
function emit_crypto_prelude() -> void {
|
||||||
|
# the 64 SHA-256 round constants (first 32 bits of the fractional parts of the
|
||||||
|
# cube roots of the first 64 primes), as signed i32.
|
||||||
|
emith("@sha256_K = private unnamed_addr constant [64 x i32] [i32 1116352408, i32 1899447441, i32 -1245643825, i32 -373957723, i32 961987163, i32 1508970993, i32 -1841331548, i32 -1424204075, i32 -670586216, i32 310598401, i32 607225278, i32 1426881987, i32 1925078388, i32 -2132889090, i32 -1680079193, i32 -1046744716, i32 -459576895, i32 -272742522, i32 264347078, i32 604807628, i32 770255983, i32 1249150122, i32 1555081692, i32 1996064986, i32 -1740746414, i32 -1473132947, i32 -1341970488, i32 -1084653625, i32 -958395405, i32 -710438585, i32 113926993, i32 338241895, i32 666307205, i32 773529912, i32 1294757372, i32 1396182291, i32 1695183700, i32 1986661051, i32 -2117940946, i32 -1838011259, i32 -1564481375, i32 -1474664885, i32 -1035236496, i32 -949202525, i32 -778901479, i32 -694614492, i32 -200395387, i32 275423344, i32 430227734, i32 506948616, i32 659060556, i32 883997877, i32 958139571, i32 1322822218, i32 1537002063, i32 1747873779, i32 1955562222, i32 2024104815, i32 -2067236844, i32 -1933114872, i32 -1866530822, i32 -1538233109, i32 -1090935817, i32 -965641998]\n")
|
||||||
|
|
||||||
|
# rotate a 32-bit word right by %n (1..31)
|
||||||
|
emith("define i32 @fn_rotr32(i32 %x, i32 %n) {\n")
|
||||||
|
emith(" %r = lshr i32 %x, %n\n %m = sub i32 32, %n\n %l = shl i32 %x, %m\n %o = or i32 %r, %l\n ret i32 %o\n}\n")
|
||||||
|
|
||||||
|
# SHA-256 of %len bytes at %msg -> the 32 raw digest bytes at %out. Pads into a
|
||||||
|
# fresh malloc'd buffer (append 0x80, zero-fill, 64-bit big-endian bit length),
|
||||||
|
# then runs the standard 64-round compression over each 512-bit block.
|
||||||
|
emith("define void @fn_sha256_buf(ptr %msg, i64 %len, ptr %out) {\n")
|
||||||
|
emith("entry:\n")
|
||||||
|
emith(" %H = alloca [8 x i32]\n %W = alloca [64 x i32]\n")
|
||||||
|
emith(" %a = alloca i32\n %b = alloca i32\n %c = alloca i32\n %d = alloca i32\n %e = alloca i32\n %f = alloca i32\n %g = alloca i32\n %h = alloca i32\n")
|
||||||
|
emith(" %ip = alloca i64\n %bp = alloca i64\n")
|
||||||
|
# padded length = ((len + 8) / 64 + 1) * 64
|
||||||
|
emith(" %e0 = add i64 %len, 8\n %e1 = lshr i64 %e0, 6\n %e2 = add i64 %e1, 1\n %pl = shl i64 %e2, 6\n")
|
||||||
|
emith(" %buf = call ptr @malloc(i64 %pl)\n")
|
||||||
|
emith(" call ptr @memset(ptr %buf, i32 0, i64 %pl)\n")
|
||||||
|
emith(" call ptr @memcpy(ptr %buf, ptr %msg, i64 %len)\n")
|
||||||
|
emith(" %pmark = getelementptr i8, ptr %buf, i64 %len\n store i8 -128, ptr %pmark\n") # 0x80
|
||||||
|
emith(" %bits = shl i64 %len, 3\n")
|
||||||
|
# write the 64-bit big-endian message length into the final 8 bytes
|
||||||
|
emith(" store i64 0, ptr %ip\n br label %lenc\n")
|
||||||
|
emith("lenc:\n %lj = load i64, ptr %ip\n %ljlt = icmp slt i64 %lj, 8\n br i1 %ljlt, label %lenb, label %hinit\n")
|
||||||
|
emith("lenb:\n")
|
||||||
|
emith(" %lj8 = mul i64 %lj, 8\n %lsh = sub i64 56, %lj8\n %lbsh = lshr i64 %bits, %lsh\n %lbb = trunc i64 %lbsh to i8\n")
|
||||||
|
emith(" %lpm8 = sub i64 %pl, 8\n %lpos = add i64 %lpm8, %lj\n %lpp = getelementptr i8, ptr %buf, i64 %lpos\n store i8 %lbb, ptr %lpp\n")
|
||||||
|
emith(" %lj1 = add i64 %lj, 1\n store i64 %lj1, ptr %ip\n br label %lenc\n")
|
||||||
|
# H := the eight initial hash values (fractional parts of the sqrt of primes)
|
||||||
|
emith("hinit:\n")
|
||||||
|
emith(" %H0 = getelementptr [8 x i32], ptr %H, i64 0, i64 0\n store i32 1779033703, ptr %H0\n")
|
||||||
|
emith(" %H1 = getelementptr [8 x i32], ptr %H, i64 0, i64 1\n store i32 -1150833019, ptr %H1\n")
|
||||||
|
emith(" %H2 = getelementptr [8 x i32], ptr %H, i64 0, i64 2\n store i32 1013904242, ptr %H2\n")
|
||||||
|
emith(" %H3 = getelementptr [8 x i32], ptr %H, i64 0, i64 3\n store i32 -1521486534, ptr %H3\n")
|
||||||
|
emith(" %H4 = getelementptr [8 x i32], ptr %H, i64 0, i64 4\n store i32 1359893119, ptr %H4\n")
|
||||||
|
emith(" %H5 = getelementptr [8 x i32], ptr %H, i64 0, i64 5\n store i32 -1694144372, ptr %H5\n")
|
||||||
|
emith(" %H6 = getelementptr [8 x i32], ptr %H, i64 0, i64 6\n store i32 528734635, ptr %H6\n")
|
||||||
|
emith(" %H7 = getelementptr [8 x i32], ptr %H, i64 0, i64 7\n store i32 1541459225, ptr %H7\n")
|
||||||
|
emith(" %nb = lshr i64 %pl, 6\n store i64 0, ptr %bp\n br label %blkc\n")
|
||||||
|
# ---- per-block loop ----
|
||||||
|
emith("blkc:\n %bi = load i64, ptr %bp\n %blt = icmp ult i64 %bi, %nb\n br i1 %blt, label %blkb, label %outp\n")
|
||||||
|
emith("blkb:\n %bi64 = shl i64 %bi, 6\n %base = getelementptr i8, ptr %buf, i64 %bi64\n")
|
||||||
|
# W[0..15] <- the block's sixteen big-endian 32-bit words
|
||||||
|
emith(" store i64 0, ptr %ip\n br label %w1c\n")
|
||||||
|
emith("w1c:\n %wi = load i64, ptr %ip\n %wilt = icmp slt i64 %wi, 16\n br i1 %wilt, label %w1b, label %w2init\n")
|
||||||
|
emith("w1b:\n")
|
||||||
|
emith(" %wi4 = shl i64 %wi, 2\n")
|
||||||
|
emith(" %wp0 = getelementptr i8, ptr %base, i64 %wi4\n %wc0 = load i8, ptr %wp0\n")
|
||||||
|
emith(" %wo1 = add i64 %wi4, 1\n %wp1 = getelementptr i8, ptr %base, i64 %wo1\n %wc1 = load i8, ptr %wp1\n")
|
||||||
|
emith(" %wo2 = add i64 %wi4, 2\n %wp2 = getelementptr i8, ptr %base, i64 %wo2\n %wc2 = load i8, ptr %wp2\n")
|
||||||
|
emith(" %wo3 = add i64 %wi4, 3\n %wp3 = getelementptr i8, ptr %base, i64 %wo3\n %wc3 = load i8, ptr %wp3\n")
|
||||||
|
emith(" %wz0 = zext i8 %wc0 to i32\n %wz1 = zext i8 %wc1 to i32\n %wz2 = zext i8 %wc2 to i32\n %wz3 = zext i8 %wc3 to i32\n")
|
||||||
|
emith(" %ws24 = shl i32 %wz0, 24\n %ws16 = shl i32 %wz1, 16\n %ws8 = shl i32 %wz2, 8\n")
|
||||||
|
emith(" %wor1 = or i32 %ws24, %ws16\n %wor2 = or i32 %wor1, %ws8\n %word = or i32 %wor2, %wz3\n")
|
||||||
|
emith(" %wwp = getelementptr [64 x i32], ptr %W, i64 0, i64 %wi\n store i32 %word, ptr %wwp\n")
|
||||||
|
emith(" %wi1 = add i64 %wi, 1\n store i64 %wi1, ptr %ip\n br label %w1c\n")
|
||||||
|
# W[16..63] <- the message schedule extension
|
||||||
|
emith("w2init:\n store i64 16, ptr %ip\n br label %w2c\n")
|
||||||
|
emith("w2c:\n %xi = load i64, ptr %ip\n %xilt = icmp slt i64 %xi, 64\n br i1 %xilt, label %w2b, label %compinit\n")
|
||||||
|
emith("w2b:\n")
|
||||||
|
emith(" %im15 = sub i64 %xi, 15\n %pm15 = getelementptr [64 x i32], ptr %W, i64 0, i64 %im15\n %w15 = load i32, ptr %pm15\n")
|
||||||
|
emith(" %r7 = call i32 @fn_rotr32(i32 %w15, i32 7)\n %r18 = call i32 @fn_rotr32(i32 %w15, i32 18)\n %sh3 = lshr i32 %w15, 3\n")
|
||||||
|
emith(" %x01 = xor i32 %r7, %r18\n %s0 = xor i32 %x01, %sh3\n")
|
||||||
|
emith(" %im2 = sub i64 %xi, 2\n %pm2 = getelementptr [64 x i32], ptr %W, i64 0, i64 %im2\n %w2v = load i32, ptr %pm2\n")
|
||||||
|
emith(" %r17 = call i32 @fn_rotr32(i32 %w2v, i32 17)\n %r19 = call i32 @fn_rotr32(i32 %w2v, i32 19)\n %sh10 = lshr i32 %w2v, 10\n")
|
||||||
|
emith(" %x02 = xor i32 %r17, %r19\n %s1 = xor i32 %x02, %sh10\n")
|
||||||
|
emith(" %im16 = sub i64 %xi, 16\n %pm16 = getelementptr [64 x i32], ptr %W, i64 0, i64 %im16\n %w16 = load i32, ptr %pm16\n")
|
||||||
|
emith(" %im7 = sub i64 %xi, 7\n %pm7 = getelementptr [64 x i32], ptr %W, i64 0, i64 %im7\n %w7 = load i32, ptr %pm7\n")
|
||||||
|
emith(" %wa1 = add i32 %w16, %s0\n %wa2 = add i32 %wa1, %w7\n %wv = add i32 %wa2, %s1\n")
|
||||||
|
emith(" %wpi = getelementptr [64 x i32], ptr %W, i64 0, i64 %xi\n store i32 %wv, ptr %wpi\n")
|
||||||
|
emith(" %xi1 = add i64 %xi, 1\n store i64 %xi1, ptr %ip\n br label %w2c\n")
|
||||||
|
# a..h <- H
|
||||||
|
emith("compinit:\n")
|
||||||
|
emith(" %cv0 = load i32, ptr %H0\n store i32 %cv0, ptr %a\n")
|
||||||
|
emith(" %cv1 = load i32, ptr %H1\n store i32 %cv1, ptr %b\n")
|
||||||
|
emith(" %cv2 = load i32, ptr %H2\n store i32 %cv2, ptr %c\n")
|
||||||
|
emith(" %cv3 = load i32, ptr %H3\n store i32 %cv3, ptr %d\n")
|
||||||
|
emith(" %cv4 = load i32, ptr %H4\n store i32 %cv4, ptr %e\n")
|
||||||
|
emith(" %cv5 = load i32, ptr %H5\n store i32 %cv5, ptr %f\n")
|
||||||
|
emith(" %cv6 = load i32, ptr %H6\n store i32 %cv6, ptr %g\n")
|
||||||
|
emith(" %cv7 = load i32, ptr %H7\n store i32 %cv7, ptr %h\n")
|
||||||
|
emith(" store i64 0, ptr %ip\n br label %rc\n")
|
||||||
|
# ---- the 64 compression rounds ----
|
||||||
|
emith("rc:\n %ri = load i64, ptr %ip\n %rlt = icmp slt i64 %ri, 64\n br i1 %rlt, label %rb, label %addH\n")
|
||||||
|
emith("rb:\n")
|
||||||
|
emith(" %av = load i32, ptr %a\n %bv = load i32, ptr %b\n %cvv = load i32, ptr %c\n %dv = load i32, ptr %d\n")
|
||||||
|
emith(" %ev = load i32, ptr %e\n %fv = load i32, ptr %f\n %gv = load i32, ptr %g\n %hv = load i32, ptr %h\n")
|
||||||
|
# S1 = rotr(e,6) ^ rotr(e,11) ^ rotr(e,25); ch = (e & f) ^ (~e & g)
|
||||||
|
emith(" %e6 = call i32 @fn_rotr32(i32 %ev, i32 6)\n %e11 = call i32 @fn_rotr32(i32 %ev, i32 11)\n %e25 = call i32 @fn_rotr32(i32 %ev, i32 25)\n")
|
||||||
|
emith(" %S1a = xor i32 %e6, %e11\n %S1 = xor i32 %S1a, %e25\n")
|
||||||
|
emith(" %ef = and i32 %ev, %fv\n %ne = xor i32 %ev, -1\n %neg = and i32 %ne, %gv\n %ch = xor i32 %ef, %neg\n")
|
||||||
|
emith(" %kp = getelementptr [64 x i32], ptr @sha256_K, i64 0, i64 %ri\n %kv = load i32, ptr %kp\n")
|
||||||
|
emith(" %wpr = getelementptr [64 x i32], ptr %W, i64 0, i64 %ri\n %wvr = load i32, ptr %wpr\n")
|
||||||
|
# temp1 = h + S1 + ch + K[i] + W[i]
|
||||||
|
emith(" %t1a = add i32 %hv, %S1\n %t1b = add i32 %t1a, %ch\n %t1c = add i32 %t1b, %kv\n %temp1 = add i32 %t1c, %wvr\n")
|
||||||
|
# S0 = rotr(a,2) ^ rotr(a,13) ^ rotr(a,22); maj = (a&b) ^ (a&c) ^ (b&c)
|
||||||
|
emith(" %a2r = call i32 @fn_rotr32(i32 %av, i32 2)\n %a13 = call i32 @fn_rotr32(i32 %av, i32 13)\n %a22 = call i32 @fn_rotr32(i32 %av, i32 22)\n")
|
||||||
|
emith(" %S0a = xor i32 %a2r, %a13\n %S0 = xor i32 %S0a, %a22\n")
|
||||||
|
emith(" %ab = and i32 %av, %bv\n %ac = and i32 %av, %cvv\n %bc = and i32 %bv, %cvv\n %mj1 = xor i32 %ab, %ac\n %maj = xor i32 %mj1, %bc\n")
|
||||||
|
emith(" %temp2 = add i32 %S0, %maj\n")
|
||||||
|
# rotate the working registers: h=g, g=f, f=e, e=d+temp1, d=c, c=b, b=a, a=temp1+temp2
|
||||||
|
emith(" store i32 %gv, ptr %h\n store i32 %fv, ptr %g\n store i32 %ev, ptr %f\n")
|
||||||
|
emith(" %newe = add i32 %dv, %temp1\n store i32 %newe, ptr %e\n")
|
||||||
|
emith(" store i32 %cvv, ptr %d\n store i32 %bv, ptr %c\n store i32 %av, ptr %b\n")
|
||||||
|
emith(" %newa = add i32 %temp1, %temp2\n store i32 %newa, ptr %a\n")
|
||||||
|
emith(" %rin = add i64 %ri, 1\n store i64 %rin, ptr %ip\n br label %rc\n")
|
||||||
|
# H[i] += the working registers
|
||||||
|
emith("addH:\n")
|
||||||
|
emith(" %fa = load i32, ptr %a\n %lH0 = load i32, ptr %H0\n %nH0 = add i32 %lH0, %fa\n store i32 %nH0, ptr %H0\n")
|
||||||
|
emith(" %fb = load i32, ptr %b\n %lH1 = load i32, ptr %H1\n %nH1 = add i32 %lH1, %fb\n store i32 %nH1, ptr %H1\n")
|
||||||
|
emith(" %fc = load i32, ptr %c\n %lH2 = load i32, ptr %H2\n %nH2 = add i32 %lH2, %fc\n store i32 %nH2, ptr %H2\n")
|
||||||
|
emith(" %fd = load i32, ptr %d\n %lH3 = load i32, ptr %H3\n %nH3 = add i32 %lH3, %fd\n store i32 %nH3, ptr %H3\n")
|
||||||
|
emith(" %fe = load i32, ptr %e\n %lH4 = load i32, ptr %H4\n %nH4 = add i32 %lH4, %fe\n store i32 %nH4, ptr %H4\n")
|
||||||
|
emith(" %ff = load i32, ptr %f\n %lH5 = load i32, ptr %H5\n %nH5 = add i32 %lH5, %ff\n store i32 %nH5, ptr %H5\n")
|
||||||
|
emith(" %fg = load i32, ptr %g\n %lH6 = load i32, ptr %H6\n %nH6 = add i32 %lH6, %fg\n store i32 %nH6, ptr %H6\n")
|
||||||
|
emith(" %fh = load i32, ptr %h\n %lH7 = load i32, ptr %H7\n %nH7 = add i32 %lH7, %fh\n store i32 %nH7, ptr %H7\n")
|
||||||
|
emith(" %binc = add i64 %bi, 1\n store i64 %binc, ptr %bp\n br label %blkc\n")
|
||||||
|
# ---- serialize H[0..7] big-endian into the 32-byte output ----
|
||||||
|
emith("outp:\n store i64 0, ptr %ip\n br label %oc\n")
|
||||||
|
emith("oc:\n %oi = load i64, ptr %ip\n %olt = icmp slt i64 %oi, 8\n br i1 %olt, label %ob, label %freeb\n")
|
||||||
|
emith("ob:\n")
|
||||||
|
emith(" %hpp = getelementptr [8 x i32], ptr %H, i64 0, i64 %oi\n %hval = load i32, ptr %hpp\n %oi4 = shl i64 %oi, 2\n")
|
||||||
|
emith(" %ob24 = lshr i32 %hval, 24\n %obb24 = trunc i32 %ob24 to i8\n %op0 = getelementptr i8, ptr %out, i64 %oi4\n store i8 %obb24, ptr %op0\n")
|
||||||
|
emith(" %ob16 = lshr i32 %hval, 16\n %obb16 = trunc i32 %ob16 to i8\n %oo1 = add i64 %oi4, 1\n %op1 = getelementptr i8, ptr %out, i64 %oo1\n store i8 %obb16, ptr %op1\n")
|
||||||
|
emith(" %ob8 = lshr i32 %hval, 8\n %obb8 = trunc i32 %ob8 to i8\n %oo2 = add i64 %oi4, 2\n %op2 = getelementptr i8, ptr %out, i64 %oo2\n store i8 %obb8, ptr %op2\n")
|
||||||
|
emith(" %obb0 = trunc i32 %hval to i8\n %oo3 = add i64 %oi4, 3\n %op3 = getelementptr i8, ptr %out, i64 %oo3\n store i8 %obb0, ptr %op3\n")
|
||||||
|
emith(" %oin = add i64 %oi, 1\n store i64 %oin, ptr %ip\n br label %oc\n")
|
||||||
|
emith("freeb:\n call void @free(ptr %buf)\n ret void\n}\n")
|
||||||
|
|
||||||
|
# one hex digit (0..15) -> its lowercase ASCII byte
|
||||||
|
emith("define i8 @fn_hex_digit(i32 %d) {\n")
|
||||||
|
emith(" %lt = icmp ult i32 %d, 10\n %base = select i1 %lt, i32 48, i32 87\n %v = add i32 %base, %d\n %c = trunc i32 %v to i8\n ret i8 %c\n}\n")
|
||||||
|
|
||||||
|
# hex-encode %n bytes at %in -> a fresh null-terminated 2n-char string
|
||||||
|
emith("define ptr @fn_hex_encode(ptr %in, i64 %n) {\n")
|
||||||
|
emith("entry:\n %ip = alloca i64\n %olen = shl i64 %n, 1\n %olen1 = add i64 %olen, 1\n %s = call ptr @malloc(i64 %olen1)\n store i64 0, ptr %ip\n br label %c\n")
|
||||||
|
emith("c:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, %n\n br i1 %lt, label %bdy, label %done\n")
|
||||||
|
emith("bdy:\n %pp = getelementptr i8, ptr %in, i64 %i\n %byte = load i8, ptr %pp\n %bz = zext i8 %byte to i32\n")
|
||||||
|
emith(" %hi = lshr i32 %bz, 4\n %lo = and i32 %bz, 15\n %hc = call i8 @fn_hex_digit(i32 %hi)\n %lc = call i8 @fn_hex_digit(i32 %lo)\n")
|
||||||
|
emith(" %oi = shl i64 %i, 1\n %o0 = getelementptr i8, ptr %s, i64 %oi\n store i8 %hc, ptr %o0\n %oi1 = add i64 %oi, 1\n %o1 = getelementptr i8, ptr %s, i64 %oi1\n store i8 %lc, ptr %o1\n")
|
||||||
|
emith(" %in1 = add i64 %i, 1\n store i64 %in1, ptr %ip\n br label %c\n")
|
||||||
|
emith("done:\n %tp = getelementptr i8, ptr %s, i64 %olen\n store i8 0, ptr %tp\n ret ptr %s\n}\n")
|
||||||
|
|
||||||
|
# SHA-256 of a null-terminated string -> 64-char hex
|
||||||
|
emith("define ptr @fn_sha256_hex(ptr %s) {\n")
|
||||||
|
emith("entry:\n %dig = alloca [32 x i8]\n %len = call i64 @strlen(ptr %s)\n %dp = getelementptr [32 x i8], ptr %dig, i64 0, i64 0\n")
|
||||||
|
emith(" call void @fn_sha256_buf(ptr %s, i64 %len, ptr %dp)\n %hex = call ptr @fn_hex_encode(ptr %dp, i64 32)\n ret ptr %hex\n}\n")
|
||||||
|
|
||||||
|
# hex of a whole null-terminated string's bytes
|
||||||
|
emith("define ptr @fn_str_hex(ptr %s) {\n")
|
||||||
|
emith(" %n = call i64 @strlen(ptr %s)\n %h = call ptr @fn_hex_encode(ptr %s, i64 %n)\n ret ptr %h\n}\n")
|
||||||
|
|
||||||
|
# xor 64 bytes of %src with the byte %pad into %dst (the HMAC key padding step)
|
||||||
|
emith("define void @fn_xor64(ptr %dst, ptr %src, i32 %pad) {\n")
|
||||||
|
emith("entry:\n %ip = alloca i64\n store i64 0, ptr %ip\n br label %c\n")
|
||||||
|
emith("c:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, 64\n br i1 %lt, label %b, label %d\n")
|
||||||
|
emith("b:\n %sp = getelementptr i8, ptr %src, i64 %i\n %sv = load i8, ptr %sp\n %sz = zext i8 %sv to i32\n %xr = xor i32 %sz, %pad\n %xb = trunc i32 %xr to i8\n %dp = getelementptr i8, ptr %dst, i64 %i\n store i8 %xb, ptr %dp\n %in = add i64 %i, 1\n store i64 %in, ptr %ip\n br label %c\n")
|
||||||
|
emith("d:\n ret void\n}\n")
|
||||||
|
|
||||||
|
# HMAC-SHA256(key, msg) -> 64-char hex (RFC 2104, block size 64).
|
||||||
|
emith("define ptr @fn_hmac_sha256_hex(ptr %key, ptr %msg) {\n")
|
||||||
|
emith("entry:\n")
|
||||||
|
emith(" %k0 = alloca [64 x i8]\n %inner = alloca [32 x i8]\n %outbuf = alloca [96 x i8]\n %fin = alloca [32 x i8]\n")
|
||||||
|
emith(" %klen = call i64 @strlen(ptr %key)\n %mlen = call i64 @strlen(ptr %msg)\n")
|
||||||
|
emith(" %k0p = getelementptr [64 x i8], ptr %k0, i64 0, i64 0\n call ptr @memset(ptr %k0p, i32 0, i64 64)\n")
|
||||||
|
# K0: a key longer than the block is replaced by its own hash; otherwise it is
|
||||||
|
# right-zero-padded to 64 bytes.
|
||||||
|
emith(" %big = icmp ugt i64 %klen, 64\n br i1 %big, label %hashk, label %copyk\n")
|
||||||
|
emith("hashk:\n call void @fn_sha256_buf(ptr %key, i64 %klen, ptr %k0p)\n br label %pads\n")
|
||||||
|
emith("copyk:\n call ptr @memcpy(ptr %k0p, ptr %key, i64 %klen)\n br label %pads\n")
|
||||||
|
emith("pads:\n")
|
||||||
|
# inner = SHA-256( (K0 ^ ipad) || msg ), ipad = 0x36
|
||||||
|
emith(" %inlen = add i64 64, %mlen\n %inbuf = call ptr @malloc(i64 %inlen)\n")
|
||||||
|
emith(" call void @fn_xor64(ptr %inbuf, ptr %k0p, i32 54)\n")
|
||||||
|
emith(" %inmsg = getelementptr i8, ptr %inbuf, i64 64\n call ptr @memcpy(ptr %inmsg, ptr %msg, i64 %mlen)\n")
|
||||||
|
emith(" %innerp = getelementptr [32 x i8], ptr %inner, i64 0, i64 0\n call void @fn_sha256_buf(ptr %inbuf, i64 %inlen, ptr %innerp)\n call void @free(ptr %inbuf)\n")
|
||||||
|
# digest = SHA-256( (K0 ^ opad) || inner ), opad = 0x5c
|
||||||
|
emith(" %outp = getelementptr [96 x i8], ptr %outbuf, i64 0, i64 0\n call void @fn_xor64(ptr %outp, ptr %k0p, i32 92)\n")
|
||||||
|
emith(" %outmsg = getelementptr i8, ptr %outbuf, i64 64\n call ptr @memcpy(ptr %outmsg, ptr %innerp, i64 32)\n")
|
||||||
|
emith(" %finp = getelementptr [32 x i8], ptr %fin, i64 0, i64 0\n call void @fn_sha256_buf(ptr %outp, i64 96, ptr %finp)\n")
|
||||||
|
emith(" %hex = call ptr @fn_hex_encode(ptr %finp, i64 32)\n ret ptr %hex\n}\n")
|
||||||
|
|
||||||
|
# constant-time equality of two null-terminated strings. Length is not secret,
|
||||||
|
# so an unequal length returns early; equal-length inputs are compared with a
|
||||||
|
# data-independent XOR-accumulate that never short-circuits.
|
||||||
|
emith("define i32 @fn_ct_streq(ptr %a, ptr %b) {\n")
|
||||||
|
emith("entry:\n %accp = alloca i32\n %ip = alloca i64\n %la = call i64 @strlen(ptr %a)\n %lb = call i64 @strlen(ptr %b)\n %eqlen = icmp eq i64 %la, %lb\n br i1 %eqlen, label %go, label %ne\n")
|
||||||
|
emith("ne:\n ret i32 0\n")
|
||||||
|
emith("go:\n store i32 0, ptr %accp\n store i64 0, ptr %ip\n br label %c\n")
|
||||||
|
emith("c:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, %la\n br i1 %lt, label %bdy, label %d\n")
|
||||||
|
emith("bdy:\n %pa = getelementptr i8, ptr %a, i64 %i\n %va = load i8, ptr %pa\n %pb = getelementptr i8, ptr %b, i64 %i\n %vb = load i8, ptr %pb\n %x = xor i8 %va, %vb\n %xz = zext i8 %x to i32\n %ac = load i32, ptr %accp\n %ao = or i32 %ac, %xz\n store i32 %ao, ptr %accp\n %in = add i64 %i, 1\n store i64 %in, ptr %ip\n br label %c\n")
|
||||||
|
emith("d:\n %finv = load i32, ptr %accp\n %z = icmp eq i32 %finv, 0\n %r = zext i1 %z to i32\n ret i32 %r\n}\n")
|
||||||
|
}
|
||||||
|
|
@ -104,6 +104,7 @@ function emit_program() -> void {
|
||||||
if g_uses_textrt { emit_text_prelude() } # @fn_str_upper/lower/trim/repeat/pad builders
|
if g_uses_textrt { emit_text_prelude() } # @fn_str_upper/lower/trim/repeat/pad builders
|
||||||
if g_uses_textrt2 { emit_text2_prelude() } # @fn_str_replace/join/split builders
|
if g_uses_textrt2 { emit_text2_prelude() } # @fn_str_replace/join/split builders
|
||||||
if g_uses_hashrt { emit_hash_prelude() } # @fn_hash_fnv1a / @fn_hash_crc32 byte hashers
|
if g_uses_hashrt { emit_hash_prelude() } # @fn_hash_fnv1a / @fn_hash_crc32 byte hashers
|
||||||
|
if g_uses_cryptort { emit_crypto_prelude() } # @fn_sha256_hex / @fn_hmac_sha256_hex + constant-time compare
|
||||||
if g_uses_datert { emit_datetime_prelude() } # @fn_days_from_civil / @fn_civil_from_days conversions
|
if g_uses_datert { emit_datetime_prelude() } # @fn_days_from_civil / @fn_civil_from_days conversions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -231,6 +231,10 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
||||||
if is_hash_ns(meth) { return emit_hash_ns(meth, e) }
|
if is_hash_ns(meth) { return emit_hash_ns(meth, e) }
|
||||||
perr(`unknown builtin Hash.{meth}`)
|
perr(`unknown builtin Hash.{meth}`)
|
||||||
}
|
}
|
||||||
|
if (ns == "Crypto") {
|
||||||
|
if is_crypto_ns(meth) { return emit_crypto_ns(meth, e) }
|
||||||
|
perr(`unknown builtin Crypto.{meth}`)
|
||||||
|
}
|
||||||
if (ns == "Vector") {
|
if (ns == "Vector") {
|
||||||
if is_vector_ns(meth) { return emit_vector_ns(meth, e) }
|
if is_vector_ns(meth) { return emit_vector_ns(meth, e) }
|
||||||
perr(`unknown builtin Vector.{meth}`)
|
perr(`unknown builtin Vector.{meth}`)
|
||||||
|
|
|
||||||
21514
selfhost/ludicc.seed.ll
21514
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
36
selfhost/tests/crypto.ludic
Normal file
36
selfhost/tests/crypto.ludic
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
program T {
|
||||||
|
entry {
|
||||||
|
# ---- Crypto.sha256 — FIPS 180-4 known-answer vectors ----
|
||||||
|
print(Crypto.sha256("")) # e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||||
|
print(Crypto.sha256("abc")) # ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
|
||||||
|
print(Crypto.sha256("The quick brown fox jumps over the lazy dog"))
|
||||||
|
# d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592
|
||||||
|
|
||||||
|
# multi-block padding edge cases: 55 bytes (fits one block), 56 (spills into a
|
||||||
|
# second), 64 (an exact block that forces a whole padding block).
|
||||||
|
print(Crypto.sha256(Text.repeat("a", 55))) # 9f4390f8d30c2dd92ec9f095b65e2b9ae9b0a925a5258e241c9f1e910f734318
|
||||||
|
print(Crypto.sha256(Text.repeat("a", 56))) # b35439a4ac6f0948b6d6f9e3c6af0f5f590ce20f1bde7090ef7970686ec6738a
|
||||||
|
print(Crypto.sha256(Text.repeat("a", 64))) # ffe054fe7ae0cb6dc65c3af9b61d5209f439851db43d0ba5997337df154668eb
|
||||||
|
|
||||||
|
# ---- Crypto.hmac_sha256 — RFC-style known answers ----
|
||||||
|
print(Crypto.hmac_sha256("key", "The quick brown fox jumps over the lazy dog"))
|
||||||
|
# f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8
|
||||||
|
print(Crypto.hmac_sha256("Jefe", "what do ya want for nothing?"))
|
||||||
|
# 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843
|
||||||
|
|
||||||
|
# ---- Crypto.verify_hmac — constant-time MAC check ----
|
||||||
|
let key = "s3cret"
|
||||||
|
let payload = "score=9001;level=12"
|
||||||
|
let mac = Crypto.hmac_sha256(key, payload)
|
||||||
|
if Crypto.verify_hmac(key, payload, mac) { print(1) } else { print(0) } # 1: untampered
|
||||||
|
if Crypto.verify_hmac(key, "score=9999;level=12", mac) { print(1) } else { print(0) } # 0: tampered payload
|
||||||
|
let forged = "0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
if Crypto.verify_hmac(key, payload, forged) { print(1) } else { print(0) } # 0: forged mac
|
||||||
|
|
||||||
|
# ---- Crypto.ct_equal + Crypto.hex ----
|
||||||
|
if Crypto.ct_equal("abc", "abc") { print(1) } else { print(0) } # 1
|
||||||
|
if Crypto.ct_equal("abc", "abd") { print(1) } else { print(0) } # 0
|
||||||
|
if Crypto.ct_equal("abc", "abcd") { print(1) } else { print(0) } # 0 (unequal length)
|
||||||
|
print(Crypto.hex("abc")) # 616263
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -409,5 +409,12 @@
|
||||||
"clock-set",
|
"clock-set",
|
||||||
"clock-advance",
|
"clock-advance",
|
||||||
"clock-reset"
|
"clock-reset"
|
||||||
|
],
|
||||||
|
"crypto": [
|
||||||
|
"crypto-sha256",
|
||||||
|
"crypto-hmac_sha256",
|
||||||
|
"crypto-verify_hmac",
|
||||||
|
"crypto-hex",
|
||||||
|
"crypto-ct_equal"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -28,6 +28,7 @@ function selfhost_frags() -> []pointer {
|
||||||
push(f, "selfhost/emit_vector.ludic")
|
push(f, "selfhost/emit_vector.ludic")
|
||||||
push(f, "selfhost/emit_text.ludic")
|
push(f, "selfhost/emit_text.ludic")
|
||||||
push(f, "selfhost/emit_hash.ludic")
|
push(f, "selfhost/emit_hash.ludic")
|
||||||
|
push(f, "selfhost/emit_crypto.ludic")
|
||||||
push(f, "selfhost/emit_list.ludic")
|
push(f, "selfhost/emit_list.ludic")
|
||||||
push(f, "selfhost/emit_ease.ludic")
|
push(f, "selfhost/emit_ease.ludic")
|
||||||
push(f, "selfhost/emit_collide.ludic")
|
push(f, "selfhost/emit_collide.ludic")
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ function cmd_selfhost_test() -> int {
|
||||||
sh_case("datetime2", "2026-08-30 07:05:09 30/08/26 0 -1 3600 5400 30 0")
|
sh_case("datetime2", "2026-08-30 07:05:09 30/08/26 0 -1 3600 5400 30 0")
|
||||||
sh_case("textsplit", "1 1 1 3 1 1 1 1 1 1 1")
|
sh_case("textsplit", "1 1 1 3 1 1 1 1 1 1 1")
|
||||||
sh_case("hash", "-2128831035 -468965076 114400290 114400290 0 -873187034 1095738169 0 1364076727 -2114883783 -845898438 -845898438 -78065325 -78057399 -3750763034362895579 -5808556873153909620 -4100651535478758590 -4100651535478758590 0 -5451962507482445012 7256831767414464289")
|
sh_case("hash", "-2128831035 -468965076 114400290 114400290 0 -873187034 1095738169 0 1364076727 -2114883783 -845898438 -845898438 -78065325 -78057399 -3750763034362895579 -5808556873153909620 -4100651535478758590 -4100651535478758590 0 -5451962507482445012 7256831767414464289")
|
||||||
|
sh_case("crypto", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592 9f4390f8d30c2dd92ec9f095b65e2b9ae9b0a925a5258e241c9f1e910f734318 b35439a4ac6f0948b6d6f9e3c6af0f5f590ce20f1bde7090ef7970686ec6738a ffe054fe7ae0cb6dc65c3af9b61d5209f439851db43d0ba5997337df154668eb f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843 1 0 0 1 0 0 616263")
|
||||||
sh_case("long", "1000000000000 1000000000001 1000000000005 3000000000000 1 1 1 -1000000000000 1000000 13")
|
sh_case("long", "1000000000000 1000000000001 1000000000005 3000000000000 1 1 1 -1000000000000 1000000 13")
|
||||||
sh_case("color", "16744512 1090486336 1090486336 8355711 8355711 8355711")
|
sh_case("color", "16744512 1090486336 1090486336 8355711 8355711 8355711")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue