Make a secret or black box function?

Maybe write the function in C and compile it into a shared library and have them call it via @ccall (or try StaticCompiler.jl)?

Actually, just providing the C source code is perhaps enough obsfucation :smiley:

10 Likes

I’d have to remember C…it’s been decades… :laughing:

1 Like

Put the source code in a separate file and tell them not to look at it?

1 Like

Perhaps IRTools.jl/README.md at master · FluxML/IRTools.jl · GitHub

2 Likes

Isn’t it enough to manually obfuscate it by changing all symbol names to random names? Perhaps together with some manual code transforms purely for obfuscating purposes. If the obfuscation is only for pedagogical purposes, maybe this is enough?

2 Likes

Suppose my function was f(x,y) = x^2+sin(y). Can you use this as an example to tell me how to use IRTools to generate some intermediate representation that is directly callable? I looked at the README for IRTools; I could see how code was turned into some “lower level” julia, but it wasn’t clear to me how to call/use that lower level stuff. In the README, I didn’t fully understand

julia> f = IRTools.func(ir); # Turn the new IR into a lambda

julia> f(nothing, 10, 5)

though that seems to be what I want to do: just cut and paste the output of @code…except that only works if you give arguments to it. Can I not define a function with the intermediate representation? If I gave that to a student, they’d be able to use it but not really understand it (i.e., black box).

As a MWE, I did this:

julia> f(x,y) = x^2 + sin(y)
f (generic function with 2 methods)

julia> ir = @code_ir f(1,1)
1: (%1, %2, %3)
  %4 = Core.apply_type(Base.Val, 2)
  %5 = (%4)()
  %6 = Base.literal_pow(Main.:^, %2, %5)
  %7 = Main.sin(%3)
  %8 = %6 + %7
  return %8

julia> g = IRTools.func(ir)
##254 (generic function with 1 method)

julia> g(nothing,2,1)
4.841470984807897

julia> f(2,1)
4.841470984807897

Seems to do the trick. But how would I copy the output of ir = @code... into a function definition? And why is nothing needed as the first argument in g? (Probably these are simple questions, but IRs are outside of my experience…)

Interesting approach. I don’t know how to call @code_ir, but if you use @code_lowered, and make some adjustments, it works. The result is hard to understand if the function is complex enough:

julia> f(x,y) = x^2 + sin(y)
f (generic function with 1 method)

julia> @code_lowered f(1.0,1.0)
CodeInfo(
1 ─ %1 = Core.apply_type(Base.Val, 2)
│   %2 = (%1)()
│   %3 = Base.literal_pow(Main.:^, x, %2)
│   %4 = Main.sin(y)
│   %5 = %3 + %4
└──      return %5
)


I removed the tabbing and replaced the % with var, to get:

julia> function g(x,y)
       var1 = Core.apply_type(Base.Val, 2)
       var2 = (var1)()
       var3 = Base.literal_pow(Main.:^, x, var2)
       var4 = Main.sin(y)
       var5 = var3 + var4
            return var5
       end
g (generic function with 1 method)

Which is the same function as f. Unless your students are used to this and/or your function is very simple, probably that is good enough. I will use it.

4 Likes

The premise is that if C code is sufficiently obfuscated by its low-level nature, as someone proposed earlier, then even lower-level IR code would be even better obfuscated. I think the approach would be that you supply Julia code that includes some representation of the IR code of your function, and that Julia code would expose the function generated by IRTools.func. I expect you can probably persist that IR code as text or a binary blob. JLD2 maybe?

I don’t know, but you could wrap g with another function that calls g passing nothing as the first argument.

Is there some way to define a function that evals the @code_native output? That is really obfuscated.

julia> f(x,y) = x + y
f (generic function with 1 method)

julia> @code_native f(1,1)
        .text
; ┌ @ REPL[51]:1 within `f'
; │┌ @ int.jl:87 within `+'
        leaq    (%rdi,%rsi), %rax
; │└
        retq
        nopw    %cs:(%rax,%rax)
; └


1 Like

This might be a stupid question, but does

(v1.1) pkg> add https://github.com/JuliaLang/Example.jl

work if the Github repo is a private repo? My guess is that it does not… :sweat_smile:

If it would, the repo wouldn’t be private :slight_smile:

5 Likes

Don’t know about native but I’d naively guess that @code_llvm should be possible (don’t ask me how though…). At least there are things like Base.llvmcall and such.

1 Like

Here is a simple obfuscation scheme that is probably adequate for students who aren’t very familiar with Julia…

Contents of save_obfuscated.jl:

using JLD2

obfuscate_string(str) = [Int(c) for c in str]

funvec = obfuscate_string(
"""
function obfuscated_function(x,y)
    x^2 + 2x*y + y^2
end
""")

@save "function_archive.jld2" {compress=true}  funvec

Contents of load_obfuscated.jl:

using JLD2

@load "function_archive.jld2" funvec
include_string(@__MODULE__, string(Char.(funvec)...))

Sample usage…

First Julia session:

julia> include("save_obfuscated.jl")

A later Julia session run by students for whom you did not supply a copy of save_obfuscated.jl:

julia> include("load_obfuscated.jl")
obfuscated_function (generic function with 1 method)

julia> obfuscated_function(3,4.0)
49.0

The file function_archive.jld2 (which you do supply to your students along with load_obfuscated.jl) is not human-readable.

13 Likes

One way is to approximate the function they should approximate yourself with a different basis/package as they are using and give them the approximation as target.

3 Likes

If you have a simple function like f(x, y) = x^2 + sin(y) in a given domain and you want it as a black box you can learn it in a neural network. It is black box because of its complexity.
Then you can give to your students a function which encapsulate the network.

6 Likes

@code_native might not be portable enough

isn’t this approach too brittle? string(Char.(funvec)...) gives away the function with minor tinkering by a curious student.

1 Like

It’s certainly not the best conceivable algorithm.