From 66969e87c91fe4699f298266ce981634839d52a4 Mon Sep 17 00:00:00 2001 From: Janis Erdmanis Date: Mon, 4 Nov 2024 23:43:11 +0200 Subject: [PATCH] pgroup optimizations --- Project.toml | 5 +- src/Curves/conversions.jl | 130 +++++++++++------------- src/Fields/abstract_fields.jl | 13 ++- src/Specs/Specs.jl | 3 - src/Specs/legacy.jl | 99 ------------------- src/Utils.jl | 179 +++++++++++++++++++++++++++++++--- src/groups.jl | 47 +++++++-- test/conversions.jl | 44 ++++++++- test/groups.jl | 23 +---- test/jacobi.jl | 38 ++++++++ test/runtests.jl | 4 + 11 files changed, 366 insertions(+), 219 deletions(-) delete mode 100644 src/Specs/legacy.jl create mode 100644 test/jacobi.jl diff --git a/Project.toml b/Project.toml index a26f352..1ca5692 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "CryptoGroups" uuid = "bc997328-bedd-407e-bcd3-5758e064a52d" authors = ["Janis Erdmanis "] -version = "0.6.0" +version = "0.6.1" [deps] CryptoPRG = "d846c407-34c1-46cb-aa27-d51818cc05e2" @@ -18,11 +18,10 @@ Primes = "0.5" julia = "1" [extras] -BitIntegers = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1" GaloisFields = "8d0d7f98-d412-5cd4-8397-071c807280aa" Polynomials = "f27b6e38-b328-58d1-80ce-0feddd5e7a45" SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["SafeTestsets", "Test", "GaloisFields", "BitIntegers", "Polynomials"] +test = ["SafeTestsets", "Test", "GaloisFields", "Polynomials"] diff --git a/src/Curves/conversions.jl b/src/Curves/conversions.jl index ad88e8a..4c57b16 100644 --- a/src/Curves/conversions.jl +++ b/src/Curves/conversions.jl @@ -1,5 +1,5 @@ # Implements point compression. Currently it is tied to AffinePoint but should be extended for generic case -using ..Fields: modulus, value, bitlength, int2octet, octet2int, octet2bits, bits2octet, tobits +using ..Fields: modulus, value, bitlength, int2octet!, octet2int, octet2bits, bits2octet, tobits using CryptoUtils: sqrt_mod_prime import ..Fields: octet @@ -27,9 +27,6 @@ function decompress_weierstrass(x::BigInt, ỹ::Bool, (a, b)::Tuple{F, F}) where return decompress_weierstrass(x, ỹ, (value(a), value(b)), p) end - -#function (::Type{P})(po::Vector{UInt8}) where P <: AbstractPoint - function Base.convert(::Type{P}, po::AbstractVector{UInt8}) where P <: AbstractPoint pc = po[1] @@ -101,109 +98,100 @@ end (::Type{ECPoint{P, S}})(po::Vector{UInt8}) where {P <: AffinePoint, S} = ECPoint{P, S}(P(po)) -function _compressed_octet(x::Vector{UInt8}, y::Vector{UInt8}, ỹ::Bool) - - if ỹ == false - return UInt8[2, x...] - elseif ỹ == true - return UInt8[3, x...] - end - -end - - -function _hybrid_octet(x::Vector{UInt8}, y::Vector{UInt8}, ỹ::Bool) - - if ỹ == false - return UInt8[6, x..., y...] - elseif ỹ == true - return UInt8[7, x..., y...] - end - -end - - -function _uncompressed_octet(x::Vector{UInt8}, y::Vector{UInt8}) - return UInt8[4, x..., y...] -end - - -# This shall be considered internal as x, y can't be arbitrary!!! -function _octet(x::BigInt, y::BigInt, N::Int; mode::Symbol = :uncompressed) # N is bitlength(modulus(field())) - +function _octet(x::BigInt, y::BigInt, N::Int; mode::Symbol = :uncompressed) if iszero(x) && iszero(y) return UInt8[0] end - - _x = int2octet(x, N) - _y = int2octet(y, N) - + + nbytes = cld(N, 8) + # Preallocate single output buffer + # Size is 1 (mode byte) + nbytes (x) + nbytes (y) for the largest case + out = Vector{UInt8}(undef, 2*nbytes + 1) + if mode == :uncompressed - - return _uncompressed_octet(_x, _y) - + out[1] = 0x04 + # Write x and y directly into the output buffer at appropriate offsets + int2octet!(@view(out[2:nbytes+1]), x) + int2octet!(@view(out[nbytes+2:2nbytes+1]), y) + written = 2*nbytes + 1 elseif mode in [:compressed, :hybrid] - - ỹ = mod(y, 2) % Bool + ỹ = mod(y, 2) % Bool if mode == :compressed - - return _compressed_octet(_x, _y, ỹ) - - elseif mode == :hybrid - - return _hybrid_octet(_x, _y, ỹ) - + out[1] = ỹ ? 0x03 : 0x02 + int2octet!(@view(out[2:nbytes+1]), x) + written = nbytes + 1 + else # mode == :hybrid + out[1] = ỹ ? 0x07 : 0x06 + int2octet!(@view(out[2:nbytes+1]), x) + int2octet!(@view(out[nbytes+2:2nbytes+1]), y) + written = 2*nbytes + 1 end - else error("Unrecognized mode $mode") end + # Return only the portion of the buffer that was written to + return resize!(out, written) end _octet(x::F, y::F; mode::Symbol = :uncompressed) where F <: PrimeField = _octet(value(x), value(y), bitlength(modulus(F)); mode) - function _octet(x::F, y::F; mode::Symbol = :uncompressed) where F <: BinaryField - if iszero(x) && iszero(y) return UInt8[0] end - + + # Get octets for x and y _x = octet(x) _y = octet(y) - + + # Calculate result size and allocate buffer + nbytes_x = length(_x) + nbytes_y = length(_y) + + # Determine output size based on mode + outsize = if mode == :compressed + nbytes_x + 1 # 1 byte for header + x + else # :uncompressed or :hybrid + nbytes_x + nbytes_y + 1 # 1 byte for header + x + y + end + + out = Vector{UInt8}(undef, outsize) + if mode == :uncompressed - - return _uncompressed_octet(_x, _y) - + # [4; x; y] + out[1] = 0x04 + copyto!(out, 2, _x, 1, nbytes_x) + copyto!(out, nbytes_x + 2, _y, 1, nbytes_y) + elseif mode in [:compressed, :hybrid] - if isstrict() - @warn "Calculation of ỹ could be wrong due to insufficient tests." + @warn "Calculation of ỹ could be wrong due to insufficient tests." end - z = y * inv(x) - - ỹ = tobits(z)[end] - + ỹ = tobits(z)[end] + if mode == :compressed + # [2/3; x] + out[1] = ỹ ? 0x03 : 0x02 + copyto!(out, 2, _x, 1, nbytes_x) - return _compressed_octet(_x, _y, ỹ) - - elseif mode == :hybrid - - return _hybrid_octet(_x, _y, ỹ) - + else # mode == :hybrid + # [6/7; x; y] + out[1] = ỹ ? 0x07 : 0x06 + copyto!(out, 2, _x, 1, nbytes_x) + copyto!(out, nbytes_x + 2, _y, 1, nbytes_y) end - else error("Unrecognized mode $mode") end + + return out end + octet(p::AbstractPoint; mode::Symbol = :uncompressed) = _octet(gx(p), gy(p); mode) iscompressable(::P) where P <: AbstractPoint = eq(P) <: Weierstrass diff --git a/src/Fields/abstract_fields.jl b/src/Fields/abstract_fields.jl index c334d9e..00d86f1 100644 --- a/src/Fields/abstract_fields.jl +++ b/src/Fields/abstract_fields.jl @@ -1,4 +1,4 @@ -import ..CryptoGroups.Utils: int2octet, octet2int, octet2bits, bits2octet +import ..CryptoGroups.Utils: int2octet!, octet2int, octet2bits, bits2octet """ abstract type Field end @@ -200,7 +200,16 @@ Base.isless(x::F, y::F) where F <: PrimeField = value(x) < value(y) Returns a byte representation of a field element according to FIPS 186-4 standart. """ octet(x::BinaryField) = bits2octet(tobits(x)) -octet(x::PrimeField) = int2octet(value(x), bitlength(modulus(x))) +#octet(x::PrimeField) = int2octet(value(x), bitlength(modulus(x))) +function octet(x::PrimeField) + + nbytes = cld(bitlength(x), 8) + buffer = Vector{UInt8}(undef, nbytes) + int2octet!(buffer, value(x), ) + + return buffer +end + # Perhaps a convert method fits better here as the type is specific (::Type{F})(x::Vector{UInt8}) where F <: PrimeField = F(octet2int(x)) diff --git a/src/Specs/Specs.jl b/src/Specs/Specs.jl index 853907d..b20311a 100644 --- a/src/Specs/Specs.jl +++ b/src/Specs/Specs.jl @@ -9,9 +9,6 @@ import ..CryptoGroups.Utils: octet2int, octet2bits, hex2bits, int2octet, bits2oc include("spec.jl") include("field_specs.jl") - -include("legacy.jl") - include("curve_constants.jl") include("modp_constants.jl") diff --git a/src/Specs/legacy.jl b/src/Specs/legacy.jl deleted file mode 100644 index ea6786f..0000000 --- a/src/Specs/legacy.jl +++ /dev/null @@ -1,99 +0,0 @@ -### Some prime group generation algorithms. References: -# + https://crypto.stackexchange.com/questions/820/how-does-one-calculate-a-primitive-root-for-diffie-hellman -# + https://math.stackexchange.com/questions/124408/finding-a-primitive-root-of-a-prime-number - -# Eventually will need to revisit this implmentation to comply with CryptoGroups standart -using ..CryptoGroups.Utils: @check -using CryptoPRG.Verificatum: PRG -using Primes: isprime, nextprime -using Random: AbstractRNG, default_rng - -function n_bit_random_number(rng::AbstractRNG, len::Integer) - max_n = ( BigInt(1) << len ) - 1 - if len > 2 - min_n = BigInt(1) << (len - 1) - return rand(rng, min_n:max_n) - end - return rand(rng, 1:max_n) -end - -function nbit_prime_of_size(rng::AbstractRNG, n_bits::Integer) - # generate a random nbit number - r = n_bit_random_number(rng, n_bits) - return nextprime(r) -end - - -rngprime(rng::AbstractRNG, N) = nbit_prime_of_size(rng, N) -rngint(rng::AbstractRNG, N) = n_bit_random_number(rng, N) - -### I could manage to get 512 security -# Something wrong with this one -function sophie_germain_group(rng::AbstractRNG, g::Integer, t::Int) - @check g!=1 - while true - q = rngprime(rng, 2*t) - p = 2*q + 1 - if g!=p-1 && isprime(p) - return MODP(; p, g, q) - end - end -end - -function generate_qp(rng::AbstractRNG, t) - while true - q = rngprime(rng, t) - #q == 1 && continue - - r = rngint(rng, t) - - p = q*r + 1 - if isprime(p) - return q, p - end - end -end - -generate_qp(t::Int) = generate_qp(default_rng(), t) - - -function dsa_standart_group(rng::AbstractRNG, tp::Int, tg::Int) - q, p = generate_qp(rng, 2*tp) - while true - u = mod(rngint(rng, tg), p) - g = powermod(u, div((p-1), q), p) - - if g!=1 && g!=0 - return MODP(; p, g, q) - end - end -end - -dsa_standart_group(tp::Int, tg::Int) = dsa_standart_group(default_rng(), tp, tg) - -# Dublicate exists in SigmaProofs, perhaps here it could be reduced to one element -# Alternativelly the GeneratorBasis module could be put back within CryptoGroups -# It may also be reasonable to deprecate generate_g, but then it would imply that parameter -# generation is out of the scope of the package. -function modp_generator_basis(prg::PRG, p::Integer, q::Integer, N::Integer; nr::Integer = 0) - - np = bitlength(p) - - 𝐭 = rand(prg, BigInt, N; n = np + nr) - - 𝐭′ = mod.(𝐭, big(2)^(np + nr)) - - 𝐡 = powermod.(𝐭′, (p - 1) ÷ q, p) - - return 𝐡 -end - -function generate_g(p::Integer, q::Integer; seed = Vector{UInt8}("SEED"), nr = 10, hasher = "sha256") - - prg = PRG(hasher; s = seed) - sp = MODP(;p, q) - - h = modp_generator_basis(prg, p, q, 1; nr) - - return h[1] -end diff --git a/src/Utils.jl b/src/Utils.jl index 133821f..e527253 100644 --- a/src/Utils.jl +++ b/src/Utils.jl @@ -1,6 +1,7 @@ module Utils import CryptoPRG: bitlength +import Base.GMP """ @check(ex, msg = nothing) @@ -20,40 +21,194 @@ macro check(ex, msg = nothing) end end +function jacobi(n::BigInt, k::BigInt) + + # Get the last limb (word) of a BigInt + get_limb(n::BigInt, i::Int) = unsafe_load(n.d, 1 + i) + + iseven(k) && throw("Argument k=$k should be odd.") + n = mod(n, k) + t = 1 + k_1 = true # k is odd by requirement + last_limb = get_limb(k, 0) + k_2 = (last_limb & 2 == 2) # second bit + k_3 = (last_limb & 4 == 4) # third bit + + while !iszero(n) + z = trailing_zeros(n) + n >>= z + # After right shift, check bits directly on the last limb + last_limb = get_limb(n, 0) + n_2 = (last_limb & 2 == 2) # second bit + + # For k ≡ 3,5 (mod 8) when z is odd + if isodd(z) && (k_2 ⊻ k_3) + t = -t + end + + # For quadratic reciprocity when both are ≡ 3 (mod 4) + if k_2 && n_2 + t = -t + end + + # Save current n's bits for next iteration's k + k_2 = n_2 + k_3 = (last_limb & 4 == 4) + n, k = k, n + n = mod(n, k) + end + return isone(k) ? t : 0 +end + + """ int2octet(x::Integer, N::Int = bitlength(x))::Vector{UInt8} Converts integer `x` into an octet where optional `N` specifies number of allocated bits for the integer encoding. """ -function int2octet(x::Integer) +function int2octet(x::Integer, N::Int) # We will need a refactor here + + error("This method will be deprecated") + #@warn "This method will be deprecated" + k = div(N, 8, RoundUp) + + # Pre-allocate the full buffer with zeros + result = zeros(UInt8, k) + + # Convert to hex and ensure even length hex = string(x, base=16) if mod(length(hex), 2) != 0 hex = string("0", hex) end - return hex2bytes(hex) + # Fill the latter part of result with the converted bytes + hex2bytes!(view(result, k-div(length(hex),2)+1:k), hex) + + return result end +function int2octet!(buffer::Union{Vector{UInt8}, SubArray{UInt8, 1}}, n::BigInt) -function int2octet(x::Integer, N::Int) + if iszero(n) + fill!(buffer, 0) + return 1 + end + + # Calculate number of bytes needed + nbits = ndigits(n, base=2) + needed_bytes = cld(nbits + (n < 0 ? 1 : 0), 8) + + # Fill leading positions with zeros + if needed_bytes < length(buffer) + fill!(@view(buffer[1:length(buffer)-needed_bytes]), 0) + end + + # Export bytes directly to the end of buffer + _, written = GMP.MPZ.export!(@view(buffer[end-needed_bytes+1:end]), n; + order = 1, # Big-endian + endian = 0, # Native endian + nails = 0 # Use all bits + ) + + return written +end - k = div(N, 8, RoundUp) +function int2octet(n::BigInt) - bytes = int2octet(x) + nbits = ndigits(n, base=2) + # Add one bit for sign in case of negative numbers + nbytes = cld(nbits + (n < 0 ? 1 : 0), 8) + + # Allocate output buffer + buffer = Vector{UInt8}(undef, nbytes) + + int2octet!(buffer, n) - pad = UInt8[0 for i in 1:(k - length(bytes))] + return buffer +end + +int2octet(n::Integer) = reverse(reinterpret(UInt8, [n])) + +function int2octet!(buffer::Union{Vector{UInt8}, SubArray{UInt8, 1}}, n::Integer) + + bytes = int2octet(n) + copyto!(buffer, @view(bytes[end-length(buffer)-1:end])) - return UInt8[pad..., bytes...] + return end + """ octet2int(x::Vector{UInt8})::BigInt octet2int(x::String)::BigInt -Converts a binary octet to a `BigInt`. In case a string is passed it is treated as hexadecimal and is converted with `hex2bytes`. +Converts a binary octet to a `BigInt`. In case a string is passed it is treated as hexadecimal and is converted with `hex2bytes`. Equivalent in represeentation as `parse(BigInt, bytes2hex(x), base=16)`. """ -octet2int(x::Vector{UInt8}) = parse(BigInt, bytes2hex(x), base=16) +function octet2int(bytes::AbstractVector{UInt8}) + isempty(bytes) && return BigInt(0) + + #result = BigInt() + n_bytes = length(bytes) + + # Calculate required limbs + limb_bytes = sizeof(GMP.Limb) + nlimbs = cld(n_bytes, limb_bytes) + + # Initialize BigInt with pre-allocated size + result = BigInt(; nbits = nlimbs * GMP.BITS_PER_LIMB) + + # Process full limbs first using direct memory access + if n_bytes >= limb_bytes + # Create pointer to input bytes for direct memory access + bytes_ptr = pointer(bytes) + + # Process full limbs + full_limbs = n_bytes ÷ limb_bytes + for i in 1:full_limbs + # Calculate offset from end of array + offset = n_bytes - i * limb_bytes + # Load limb directly from memory with proper byte order + limb = unsafe_load(Ptr{GMP.Limb}(bytes_ptr + offset)) + # Handle endianness if needed + if ENDIAN_BOM == 0x04030201 + limb = ntoh(limb) + end + unsafe_store!(result.d, limb, i) + end + + # Handle remaining bytes in the last partial limb + remaining_bytes = n_bytes % limb_bytes + if remaining_bytes > 0 + limb = zero(GMP.Limb) + for j in 1:remaining_bytes + shift = (remaining_bytes - j) * 8 + limb |= GMP.Limb(bytes[j]) << shift + end + unsafe_store!(result.d, limb, nlimbs) + end + else + # Handle case when input is smaller than a limb + limb = zero(GMP.Limb) + for j in 1:n_bytes + shift = (n_bytes - j) * 8 + limb |= GMP.Limb(bytes[j]) << shift + end + unsafe_store!(result.d, limb, 1) + end + + # Set the correct size (number of non-zero limbs) + actual_limbs = nlimbs + while actual_limbs > 0 && unsafe_load(result.d, actual_limbs) == 0 + actual_limbs -= 1 + end + result.size = actual_limbs + + return result +end + +#octet2int(x::Vector{UInt8}) = parse(BigInt, bytes2hex(x), base=16) + octet2int(x::String) = octet2int(hex2bytes(x)) octet2int(x::NTuple{N, UInt8}) where N = octet2int([x...]) @@ -327,7 +482,6 @@ StaticNamedTuple(; kwargs...) = StaticNamedTuple(NamedTuple((key, static(value)) Base.propertynames(x::StaticNamedTuple) = propertynames(getfield(x, :args)) Base.getproperty(x::StaticNamedTuple, sym::Symbol) = dynamic(getfield(getfield(x, :args), sym)) - static(; kwargs...) = StaticNamedTuple(; kwargs...) dynamic(x::StaticNamedTuple) = NamedTuple((key, dynamic(value)) for (key, value) in pairs(getfield(x, :args))) @@ -340,6 +494,9 @@ function Base.show(io::IO, x::StaticNamedTuple) end -export static, dynamic, @bin_str, @hex_str, octet2int, int2octet, octet2bits, bits2octet + + + +export static, dynamic, @bin_str, @hex_str, octet2int, int2octet, int2octet!, octet2bits, bits2octet end diff --git a/src/groups.jl b/src/groups.jl index b55e49f..a8268c5 100644 --- a/src/groups.jl +++ b/src/groups.jl @@ -1,6 +1,8 @@ -using .Utils: int2octet, octet2int -import .Fields: value, modulus, octet -using .Curves: ECPoint, gx, gy +using .Utils: int2octet!, octet2int, jacobi +import .Fields: value, modulus, octet, bitlength +using .Curves: ECPoint, gx, gy, field +using Primes: isprime +using CryptoUtils """ abstract type Group end @@ -52,7 +54,6 @@ Computes inverse of the group element so that `inv(g) * g == one(G)` """ Base.inv(g::G) where G <: Group = g^(order(G) - 1) -#import Base./ """ /(x::G, y::G)::G where G <: Group @@ -171,6 +172,10 @@ Base.:(==)(x::G, y::G) where G <: ECGroup = x.x == y.x modulus(::Type{ECGroup{P}}) where P <: ECPoint = modulus(P) +# this method has somewhat controversial meaning +bitlength(::Type{ECGroup{P}}) where P <: ECPoint = bitlength(field(P)) +bitlength(g::G) where G <: ECGroup = bitlength(G) + name(::Type{ECGroup}) = nothing name(::Type{ECGroup{P}}) where P <: ECPoint = name(P) @@ -198,6 +203,20 @@ Computes remainder of the elliptic curve point """ Base.rem(x::ECGroup, q::Integer) = rem(x.x, q) + +""" +Verifies membership in a prime-order group with optimizations for common cases. +""" +function verify_pgroup_membership(x::BigInt, modulus::BigInt, order::BigInt) + # Assumes modulus to be a prime!!! + + if modulus == order * 2 + 1 + return jacobi(x, modulus) == 1 + else + return powermod(x, order, modulus) == 1 + end +end + """ struct PGroup{S} <: Group g::BigInt @@ -250,8 +269,9 @@ struct PGroup{S} <: Group isstrict() ? throw(ArgumentError(msg)) : @warn msg end elseif !isnothing(_order) - 0 < x < S.p || throw(ArgumentError("Element $x is not in range of the prime group with modulus $_modulus")) - powermod(x, _order, _modulus) == 1 || throw(ArgumentError("Element $x is not an element of prime group with order $_order and modulus $_modulus")) + 0 < x < _modulus || throw(ArgumentError("Element $x is not in range of the prime group with modulus $_modulus")) + + verify_pgroup_membership(x, _modulus, _order) == 1 || throw(ArgumentError("Element $x is not an element of prime group with order $_order and modulus $_modulus")) end end @@ -271,7 +291,13 @@ Base.one(::PGroup{S}) where S = one(PGroup{S}) Converts modulus prime group element into octet representation. A padding is added to match the length of modulus. """ -octet(x::PGroup) = int2octet(value(x), bitlength(modulus(x))) +function octet(x::PGroup) + + bytes = Vector{UInt8}(undef, cld(bitlength(x), 8)) + int2octet!(bytes, value(x)) + + return bytes +end Base.convert(group::Type{<:PGroup}, element::Vector{UInt8}; allow_one::Bool=false) = convert(group, octet2int(element); allow_one) @@ -280,10 +306,13 @@ Base.convert(group::Type{<:PGroup}, element::Vector{UInt8}; allow_one::Bool=fals Modulus of a prime group. It is not recommended to depend on this method in the codebase as it destroys polymorphism. """ -modulus(::Type{PGroup{S}}) where S = BigInt(S.p) +@generated modulus(::Type{PGroup{S}}) where S = BigInt(S.p) modulus(::G) where G <: PGroup = modulus(G) -order(::Type{PGroup{S}}) where S = S.q isa Nothing ? nothing : BigInt(S.q) +bitlength(::Type{G}) where G <: PGroup = bitlength(modulus(G)) +bitlength(x::G) where G <: PGroup = bitlength(G) + +@generated order(::Type{PGroup{S}}) where S = S.q isa Nothing ? nothing : BigInt(S.q) name(::Type{PGroup}) = nothing diff --git a/test/conversions.jl b/test/conversions.jl index a916799..f225521 100644 --- a/test/conversions.jl +++ b/test/conversions.jl @@ -5,6 +5,7 @@ import CryptoGroups.Specs: ECP, EC2N, PB, point import CryptoGroups: octet, value, concretize_type, GroupSpec import CryptoGroups.Curves: AffinePoint + let x = UInt8[0, 129] @test bits2octet(octet2bits(x)) == x @@ -16,11 +17,11 @@ let @test octet2bits(x, 13) == octet2bits(x)[4:end] end -@test int2octet(123456789, 4) == hex"075BCD15" +#@test int2octet(123456789, 4) == hex"075BCD15" @test octet2int(hex"0003ABF1CD") == 61600205 -@test int2octet(94311, 17) == hex"017067" # I could deprecate +#@test int2octet(94311, 17) == hex"017067" # I could deprecate @test bits2octet(bin"11011011011101111001101111110110111110001") == hex"01B6EF37EDF1" @@ -32,6 +33,45 @@ end @test octet2int(bits2octet(bin"11111111001000010011110000110011110101110")) == 2191548508078 +function test_bigint2bytes(x::BigInt) + + hex = string(x, base=16) + if mod(length(hex), 2) != 0 + hex = string("0", hex) + end + + bytes = hex2bytes(hex) + + @test int2octet(x) == bytes +end + +function test_bytes2bigint(x::BigInt) + bytes = int2octet(x) + @test octet2int(bytes) == x +end + +@test int2octet(BigInt(0)) == UInt8[0] + +for i in BigInt[1, + 2, + 28527, + 2271661837, + 10220473608088, + 155378122852724392868, + 67711243447462057491300654662, + 850788629110566047430375891739932349, + 13861665429400309259090417042216659399253, + 181982173945937697230958265759545531627346454, + 63801187789356693097942198067182712075804486362, + 10650276354463219896828481064994007532874019652885, + 4927817297841055714930438280818688523583617802629668] + + test_bigint2bytes(i) + test_bytes2bigint(i) + +end + + let prime_curve = ECP( diff --git a/test/groups.jl b/test/groups.jl index b4ee00d..f6eea33 100644 --- a/test/groups.jl +++ b/test/groups.jl @@ -1,5 +1,6 @@ using Test import CryptoGroups: PGroup, @PGroup, order, modulus, value, concretize_type, Specs, generator, octet +import CryptoUtils G = @PGroup{p = 23, q = 11} q = order(G) @@ -94,25 +95,9 @@ let testgroup(g) end -### PGroup generation algorithms for -using Random: MersenneTwister -rng = MersenneTwister(0) - -# Broken -let -modp_spec = Specs.sophie_germain_group(rng, 2, 100) -G = concretize_type(PGroup, modp_spec) -# g = G(generator(modp_spec)) -# testgroup(g) -end - -### Seems to have an issue with large numbers -let -modp_spec = Specs.dsa_standart_group(rng, 10, 10) -G = concretize_type(PGroup, modp_spec) -g = G(generator(modp_spec)) +### Safe prime generation via CryptoUtils +p = CryptoUtils.safe_prime(100) +g = @PGroup{p = p, q = div(p - 1, 2)}(4) testgroup(g) -end - diff --git a/test/jacobi.jl b/test/jacobi.jl new file mode 100644 index 0000000..cb4933e --- /dev/null +++ b/test/jacobi.jl @@ -0,0 +1,38 @@ +using Test +using CryptoGroups +using CryptoUtils + +# Helper function to test the implementation +function test_jacobi(jacobi::Function) + # Test cases: (x, y, expected_result) + test_cases = [ + (1, 1, 1), + (1, 3, 1), + (2, 3, -1), + (1, 5, 1), + (2, 5, -1), + (5, 9, 1), + (7, 9, 1), + (0, 3, 0), + #(-1, 3, 1), + ] + + for (x, y, expected) in test_cases + @test jacobi(BigInt(x), BigInt(y)) == expected + end +end + +test_jacobi(CryptoUtils.jacobi) +test_jacobi(CryptoGroups.Utils.jacobi) + +for i in 1:100 + + x = rand(1:BigInt(2)^i) + p = CryptoUtils.random_prime(i) + + if p == 2 + continue + end + + @test CryptoGroups.Utils.jacobi(x, p) == CryptoUtils.jacobi(x, p) +end diff --git a/test/runtests.jl b/test/runtests.jl index e90d01c..130caf9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -22,6 +22,10 @@ end include("conversions.jl") end +@safetestset "Testing Jacoby symbol" begin + include("jacobi.jl") +end + @safetestset "Testing group API" begin include("groups.jl") end