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
  ...
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