Skip to content

Commit 8d445f5

Browse files
committed
Merge branch '19-add-tiny-hash-function-for-small-prime-fields'
2 parents 8955f81 + 115f1b4 commit 8d445f5

5 files changed

Lines changed: 179 additions & 11 deletions

File tree

README.md

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,60 @@ The project includes:
3636

3737
```julia
3838
using TinyCrypto
39+
import TinyCrypto: is_identity
3940

40-
# Find a suitable Weierstrass curve over small field primes and coefficient ranges
41+
# Find a suitable Weierstrass curve over small field primes and coefficient ranges with iterative method
42+
# general syntax: curve-name(prime-field-range, parameter-range₁, ..., parameter-rangeₙ, max_cofactor=8)
4143
curve = Weierstrass(97:103, 10:15, 2:7) # (prime range, a range, b range)
42-
# Output: Weierstrass curve: y² = x³ + 10x + 3 |𝔽₉₇ with order: 101 and 𝔾(0,10)
44+
# Output: Weierstrass{𝔽₉₇}: y² = x³ + 10x + 3 | 𝔾(0,10), q = 101, h = 1, #E = 10
4345

44-
# Get all curve points
45-
E = curve_points(curve)
46+
E = curve_points(curve) # Get all curve points
4647
# → 101-element Vector{ECPoint₁₂₈}: (0𝔽₉₇,10𝔽₉₇), (0𝔽₉₇,87𝔽₉₇), ..., (96𝔽₉₇,63𝔽₉₇), (∞,∞)
48+
S = subgroup_points(curve) # same as `curve_points` as #E(𝔽₉₇) ∈ primes
49+
50+
curve = Montgomery(30:40, 8:40, 3:40)
51+
# Montgomery{𝔽₃₇}: 8y² = x³ + 3x² + x | 𝔾(15,2), q = 11, h = 4, #E = 44
52+
S = subgroup_points(curve) # when cofactor greater than one, there are multiple sub groups
53+
E = curve_points(curve)
54+
55+
curve = TwistedEdwards(50:100, 1:20, 1:20)
56+
# TwistedEdwards{𝔽₉₇}: 3x² + y² = 1 + 10x²y² | 𝔾(48,93), q = 29, h = 4, #E = 116
57+
58+
curve = Edwards(50:100, 1:20, max_cofactor=8)
59+
# Edwards{𝔽₇₉}: 1x² + y² = 1 + 6x²y² | 𝔾(7,58), q = 23, h = 4, #E = 92
60+
is_singular(curve) # false
61+
62+
## Point arithmetic on curve
63+
𝔾 = curve.G # (7𝔽₇₉,58𝔽₇₉) ∈ Edwards{Fp{UInt128, 79}}
64+
𝔾 + 𝔾 + 𝔾 + 𝔾 # (68𝔽₇₉,67𝔽₇₉) point addition on a cyclic group
65+
2𝔾 # (31𝔽₇₉,51𝔽₇₉)
66+
2𝔾 == 𝔾 + 𝔾 # true
67+
68+
𝒪 = identity(curve) # identity point on Edwards curve variant: (0𝔽₇₉,1𝔽₇₉)
69+
𝒪 + 𝔾 == 𝔾 # true
70+
𝔾 + 𝒪 == 𝔾 # true
71+
72+
# direct construction from paramters:
73+
const 𝔽₃₁ = 𝔽ₚ{UInt8, 31} # define field prime 𝔽ₚ for the abelian group, or use non-unicode `Fp{base_type, prime_number}`
74+
Weierstrass{𝔽₃₁}(6, 9, 37, 1, (0,3)) # Weierstrass{𝔽₃₁}: y² = x³ + 6x + 9 | 𝔾(0,3), q = 37, h = 1, #E = 37
75+
# or pass the 𝔽ₚ field prime directly as in:
76+
curve = Weierstrass(31, 6, 9, 37, 1, (0,3)) # Weierstrass{𝔽₃₁}: y² = x³ + 6x + 9 | 𝔾(0,3), q = 37, h = 1, #E = 37
77+
78+
𝒪 = identity(curve) # (∞,∞) Weierstrass curve identity is infinity
79+
is_identity(𝒪) # true
80+
= infinity(curve) # (∞,∞) as expected
81+
is_infinity(∞) # true
82+
83+
E = curve_points(curve) #
84+
is_point_on_curve(E[1], curve) # true
85+
is_point_on_curve(curve.G, curve) # true, G generator point defines the cyclic group, which in this case the entire abelien group
86+
4787

48-
# ECDSA example
49-
private_key = 42
50-
msg = 33
5188

52-
public_key = private_key2public(private_key, curve) # ECPoint on curve
53-
r, s, v = ecdsa_sign(curve, private_key, msg)
54-
ecdsa_verify(curve, public_key, r, s, v) # true
89+
## Tiny Hash (not for cryptographic use, for obvious reasons)
90+
H("byte size hash of a string") # hashes string to a byte, given the abelian group is byte size
91+
H₁₆("16 bit hash") # in case you need more hash space
92+
H₈("is same") == H("is same") # true, it is just a alias, smae as H8
5593
```
5694

5795
## Installation

src/TinyCrypto.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,24 @@ module TinyCrypto
33
include("macroes.jl")
44
include("utils.jl")
55
include("field.jl")
6+
include("core/hash.jl")
67

78
using .Macroes
89
using .Field
910
using .Utils
11+
using .Hash
1012

1113
include("core/ecpoint.jl")
1214
include("curves/weierstrass.jl")
1315
include("curves/montgomery.jl")
1416
include("curves/edwards.jl")
1517
# Re-export core symbols for public API
1618
export @define, @attach
19+
export H, H₈, H₁₆, H8, H16
1720
export Fp, 𝔽ₚ
1821
export is_prime, primes, mod_inverse
1922
export Point, ∞, Curve, ECPoint, Infinity
2023
export Weierstrass, Montgomery, Edwards, TwistedEdwards
21-
export infinity, is_infinity
24+
export infinity, is_infinity, identity, is_identity
2225
export point_add, scalar_mult, curve_points, subgroup_points, is_generator, is_point_on_curve, inverse, is_singular
2326
end

src/core/hash.jl

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
module Hash
2+
export H, H₈,H₁₆, H8, H16
3+
4+
const INIT8 = UInt8[0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0]
5+
const K8 = UInt8[0x1f, 0x2b, 0x3c, 0x4d, 0x5e, 0x6a, 0x7d, 0x8e]
6+
7+
rotate(x::UInt8, n::Int) = ((x >> n) | (x << (8 - n))) % UInt8
8+
rotate(x::UInt16, n::Int) = ((x >> n) | (x << (16 - n))) % UInt16
9+
10+
function pad8(msg::Vector{UInt8})
11+
len = length(msg)
12+
padded = copy(msg)
13+
push!(padded, 0x80)
14+
while (length(padded) % 8) != 7
15+
push!(padded, 0x00)
16+
end
17+
push!(padded, UInt8(len % 256))
18+
return padded
19+
end
20+
21+
function compress_block8(block::Vector{UInt8}, state::Vector{UInt8})
22+
W = copy(block)
23+
for i in 1:8
24+
W[i] ⊻= K8[i]
25+
W[i] = rotate(W[i], i % 8 + 1)
26+
end
27+
for i in 1:8
28+
state[i] = rotate(state[i] W[i], i)
29+
end
30+
end
31+
32+
function tiny_hash8(msg::Vector{UInt8})
33+
padded = pad8(msg)
34+
state = copy(INIT8)
35+
for i in 1:8:length(padded)
36+
block = padded[i:i+7]
37+
compress_block8(block, state)
38+
end
39+
result = state[1]
40+
for i in 2:8
41+
result ⊻= rotate(state[i], i)
42+
end
43+
return result
44+
end
45+
tiny_hash8(msg::String) = tiny_hash8(Vector{UInt8}(codeunits(msg)))
46+
47+
function tiny_hash16(msg::Vector{UInt8})
48+
acc = UInt16(0xabcd)
49+
for (i, byte) in enumerate(msg)
50+
acc ⊻= UInt16(byte) << (i % 8)
51+
acc = (acc << 5) (acc >> 3)
52+
end
53+
return acc
54+
end
55+
tiny_hash16(msg::String) = tiny_hash16(Vector{UInt8}(codeunits(msg)))
56+
57+
function H8(msg::Union{String, Vector{UInt8}})
58+
bytes = msg isa String ? Vector{UInt8}(codeunits(msg)) : msg
59+
return tiny_hash8(bytes)
60+
end
61+
62+
function H16(msg::Union{String, Vector{UInt8}})
63+
bytes = msg isa String ? Vector{UInt8}(codeunits(msg)) : msg
64+
return tiny_hash16(bytes)
65+
end
66+
67+
H(msg::Union{String, Vector{UInt8}}) = H8(msg)
68+
H₈(msg::Union{String, Vector{UInt8}}) = H8(msg)
69+
H₁₆(msg::Union{String, Vector{UInt8}}) = H16(msg)
70+
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ using Test
22
using TinyCrypto
33

44
@testset "Curve tests" begin
5+
include("test_hash.jl")
56
include("test_field.jl")
67
include("test_montgomery.jl")
78
include("test_edwards.jl")

test/test_hash.jl

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using Test
2+
using TinyCrypto: H, H8, H16, H₈, H₁₆
3+
4+
@testset "Hash - TinyHash API" begin
5+
6+
@testset "Determinism" begin
7+
@test H("msg") == H("msg")
8+
@test H8("abc") == H8("abc")
9+
@test H16("abc") == H16("abc")
10+
end
11+
12+
@testset "Types & Output Range" begin
13+
@test H8("") isa UInt8
14+
@test H16("") isa UInt16
15+
@test 0x00 <= H8("abc") <= 0xff
16+
@test 0x0000 <= H16("abc") <= 0xffff
17+
end
18+
19+
@testset "Unicode and Multibyte Strings" begin
20+
@test H("𝒞rypto") isa UInt8
21+
@test H16("éçüΩ") != H16("ecuo")
22+
end
23+
24+
@testset "Empty input" begin
25+
@test H8("") == H(collect(UInt8, ""))
26+
@test H16("") == H16(UInt8[])
27+
end
28+
29+
@testset "Single and Repeated Characters" begin
30+
@test H8("a") == H("a")
31+
@test H8("aaaaaaaa") != H8("aaaaaaaaa") # should trigger padding into another block
32+
@test H16("z"^1000) isa UInt16
33+
end
34+
35+
@testset "Vectors of UInt8" begin
36+
@test H8(UInt8[0x61, 0x62, 0x63]) == H8("abc")
37+
@test H16(UInt8[0x61, 0x62, 0x63]) == H16("abc")
38+
end
39+
40+
@testset "Aliases" begin
41+
str = "hello world"
42+
@test H(str) == H8(str)
43+
@test H₈(str) == H8(str)
44+
@test H₁₆(str) == H16(str)
45+
end
46+
47+
@testset "Corner padding boundaries" begin
48+
# Pad to block sizes: exact 7, 8, 14, 15, etc.
49+
for n in (0, 1, 6, 7, 8, 13, 14, 15, 16, 100)
50+
msg = "a"^n
51+
@test H8(msg) isa UInt8
52+
@test H16(msg) isa UInt16
53+
end
54+
end
55+
56+
end

0 commit comments

Comments
 (0)