I was surprised finding that base64encode does not work for BigInt. Can someone help?
julia> using Base64
julia> base64encode(BigInt(1234123))
ERROR: MethodError: no method matching write(::Base64EncodePipe, ::BigInt)
Closest candidates are:
write(::IO, ::Any) at io.jl:672
write(::IO, ::Any, ::Any...) at io.jl:673
write(::IO, ::Union{Float16, Float32, Float64, Int128, Int16, Int32, Int64, UInt128, UInt16, UInt32, UInt64}) at io.jl:686
...
Stacktrace:
[1] write(io::Base64EncodePipe, x::BigInt)
@ Base ./io.jl:672
[2] base64encode(f::Function, args::BigInt; context::Nothing)
@ Base64 /nix/store/0fxg4zpsp5rbjfxn3hic1534rhagymmr-julia-1.8.5/share/julia/stdlib/v1.8/Base64/src/encode.jl:209
[3] base64encode(args::BigInt; context::Nothing)
@ Base64 /nix/store/0fxg4zpsp5rbjfxn3hic1534rhagymmr-julia-1.8.5/share/julia/stdlib/v1.8/Base64/src/encode.jl:216
[4] base64encode(args::BigInt)
@ Base64 /nix/store/0fxg4zpsp5rbjfxn3hic1534rhagymmr-julia-1.8.5/share/julia/stdlib/v1.8/Base64/src/encode.jl:216
[5] top-level scope
@ REPL[9]:1
The basic issue here is that write
(called by default from base64encode
) is not defined for BigInt
because we don’t have any canonical binary representation for a BigInt
. But you can still use BigInt
with base64 I/O if you pick what format you want to write (and read).
For example, you could use the Serialization
standard library:
julia> using Serialization, Base64
julia> x = factorial(big(100))
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
julia> str = base64encode(serialize, x)
"N0pMEQQAAAA0EAEGQmlnSW50H06eAQNHTVBEOSFZMWpjeE1aVTJjVjRsSkI4R3NacXloekNRb0ZHOVVFQ0NzR3AwSVJDdjh1SnpLUjdMV0gxY0tMenFaYlRaNml2S0dBQ0txZXdmRVhrbjVmcFM5UHBtajIwMDA="
julia> deserialize(IOBuffer(base64decode(str)))
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
which works simply by reading/writing the BigInt
from/to the stream as a string in base=62. (62 is the largest base supported natively by GMP’s string-conversion functions.)
You could also simply call str = base64encode(println, x)
to use base=10
(which is less compact), of course, and then call parse(BigInt, readline(IOBuffer(base64decode(str))))
to read it back. Or use any other base that you want. The point is that you have to pick a format.
4 Likes
Thank you very much. I have one followup question:
Am I right, that I need to use base=10 if I want to base64encode the bigint to be used as modulus in an official json web token?
The RFC 7518: JSON Web Algorithms (JWA) states It is represented as a Base64urlUInt-encoded value.
I think you probably want something like this (making use of @stevengj’s to_bytes
):
julia> function to_bytes(n::BigInt; bigendian::Bool=true)
bytes = Vector{UInt8}(undef, (Base.GMP.MPZ.sizeinbase(n, 2) + 7) ÷ 8)
order = bigendian ? Cint(1) : Cint(-1)
count = Ref{Csize_t}()
@ccall "libgmp".__gmpz_export(bytes::Ptr{UInt8}, count::Ref{Csize_t}, order::Cint,
1::Csize_t, 1::Cint, 0::Csize_t, n::Ref{BigInt})::Ptr{UInt8}
@assert count[] ≤ length(bytes)
return resize!(bytes, count[])
end
to_bytes (generic function with 1 method)
julia> function base64urlencode(x::BigInt)
@assert x >= 0
base64urlencode(x == 0 ? [0x0] : to_bytes(x))
end
base64urlencode (generic function with 2 methods)
julia> function base64urlencode(bytes::Vector{UInt8})
str = base64encode(bytes)
return replace(str, '=' => "", '+' => '-', '/' => '_')
end
base64urlencode (generic function with 2 methods)
julia> base64urlencode(big"0")
"AA"
julia> base64urlencode(big"1234")
"BNI"
julia> base64urlencode(big"999999999")
"O5rJ_w"
Refs:
1 Like
In Julia 1.9 you can use something like digits!(Vector{UInt8}(undef, ndigits(n, base=256)), n, base=256)
, which now uses gmz_export
. Note that UInt8
only works for n ≥ 0
, however. You can also (in Julia 1.9) call the low-level Base.GMP.MPZ.export!
directly for more control over byte order etcetera.
1 Like