How to base64encode a BigInt?

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
 [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))

julia> str = base64encode(serialize, x)

julia> deserialize(IOBuffer(base64decode(str)))

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.


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[])
to_bytes (generic function with 1 method)

julia> function base64urlencode(x::BigInt)
           @assert x >= 0
           base64urlencode(x == 0 ? [0x0] : to_bytes(x))
base64urlencode (generic function with 2 methods)

julia> function base64urlencode(bytes::Vector{UInt8})
           str = base64encode(bytes)
           return replace(str, '=' => "", '+' => '-', '/' => '_')
base64urlencode (generic function with 2 methods)

julia> base64urlencode(big"0")

julia> base64urlencode(big"1234")

julia> base64urlencode(big"999999999")


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