Initial commit
This commit is contained in:
74
challenge/generic.go
Normal file
74
challenge/generic.go
Normal file
@@ -0,0 +1,74 @@
|
||||
//go:build !tinygo
|
||||
|
||||
package challenge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
func MakeChallengeCall(ctx context.Context, mod api.Module, in MakeChallengeInput) (*MakeChallengeOutput, error) {
|
||||
makeChallengeFunc := mod.ExportedFunction("MakeChallenge")
|
||||
malloc := mod.ExportedFunction("malloc")
|
||||
free := mod.ExportedFunction("free")
|
||||
|
||||
inData, err := json.Marshal(in)
|
||||
|
||||
mallocResult, err := malloc.Call(ctx, uint64(len(inData)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer free.Call(ctx, mallocResult[0])
|
||||
if !mod.Memory().Write(uint32(mallocResult[0]), inData) {
|
||||
return nil, errors.New("could not write memory")
|
||||
}
|
||||
result, err := makeChallengeFunc.Call(ctx, uint64(NewAllocation(uint32(mallocResult[0]), uint32(len(inData)))))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resultPtr := Allocation(result[0])
|
||||
outData, ok := mod.Memory().Read(resultPtr.Pointer(), resultPtr.Size())
|
||||
if !ok {
|
||||
return nil, errors.New("could not read result")
|
||||
}
|
||||
defer free.Call(ctx, uint64(resultPtr.Pointer()))
|
||||
|
||||
var out MakeChallengeOutput
|
||||
err = json.Unmarshal(outData, &out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func VerifyChallengeCall(ctx context.Context, mod api.Module, in VerifyChallengeInput) (VerifyChallengeOutput, error) {
|
||||
verifyChallengeFunc := mod.ExportedFunction("VerifyChallenge")
|
||||
malloc := mod.ExportedFunction("malloc")
|
||||
free := mod.ExportedFunction("free")
|
||||
|
||||
inData, err := json.Marshal(in)
|
||||
|
||||
mallocResult, err := malloc.Call(ctx, uint64(len(inData)))
|
||||
if err != nil {
|
||||
return VerifyChallengeOutputError, err
|
||||
}
|
||||
defer free.Call(ctx, mallocResult[0])
|
||||
if !mod.Memory().Write(uint32(mallocResult[0]), inData) {
|
||||
return VerifyChallengeOutputError, errors.New("could not write memory")
|
||||
}
|
||||
result, err := verifyChallengeFunc.Call(ctx, uint64(NewAllocation(uint32(mallocResult[0]), uint32(len(inData)))))
|
||||
if err != nil {
|
||||
return VerifyChallengeOutputError, err
|
||||
}
|
||||
|
||||
return VerifyChallengeOutput(result[0]), nil
|
||||
}
|
||||
|
||||
func PtrToBytes(ptr uint32, size uint32) []byte { panic("not implemented") }
|
||||
func BytesToPtr(s []byte) (uint32, uint32) { panic("not implemented") }
|
||||
func BytesToLeakedPtr(s []byte) (uint32, uint32) { panic("not implemented") }
|
||||
func PtrToString(ptr uint32, size uint32) string { panic("not implemented") }
|
||||
func StringToPtr(s string) (uint32, uint32) { panic("not implemented") }
|
||||
func StringToLeakedPtr(s string) (uint32, uint32) { panic("not implemented") }
|
||||
123
challenge/interface.go
Normal file
123
challenge/interface.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package challenge
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const ChallengeKeySize = sha256.Size
|
||||
|
||||
type MakeChallenge func(in Allocation) (out Allocation)
|
||||
|
||||
type Allocation uint64
|
||||
|
||||
func NewAllocation(ptr, size uint32) Allocation {
|
||||
return Allocation((uint64(ptr) << uint64(32)) | uint64(size))
|
||||
}
|
||||
|
||||
func (p Allocation) Pointer() uint32 {
|
||||
return uint32(p >> 32)
|
||||
}
|
||||
func (p Allocation) Size() uint32 {
|
||||
return uint32(p)
|
||||
}
|
||||
|
||||
func MakeChallengeDecode(callback func(in MakeChallengeInput, out *MakeChallengeOutput), in Allocation) (out Allocation) {
|
||||
outStruct := &MakeChallengeOutput{}
|
||||
var inStruct MakeChallengeInput
|
||||
|
||||
inData := PtrToBytes(in.Pointer(), in.Size())
|
||||
|
||||
err := json.Unmarshal(inData, &inStruct)
|
||||
if err != nil {
|
||||
outStruct.Code = 500
|
||||
outStruct.Error = err.Error()
|
||||
} else {
|
||||
outStruct.Code = 200
|
||||
outStruct.Headers = make(http.Header)
|
||||
|
||||
func() {
|
||||
// encapsulate err
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
if outStruct.Code == 200 {
|
||||
outStruct.Code = 500
|
||||
}
|
||||
if err, ok := recovered.(error); ok {
|
||||
outStruct.Error = err.Error()
|
||||
} else {
|
||||
outStruct.Error = fmt.Sprintf("%v", recovered)
|
||||
}
|
||||
}
|
||||
}()
|
||||
callback(inStruct, outStruct)
|
||||
}()
|
||||
}
|
||||
|
||||
if len(outStruct.Headers) == 0 {
|
||||
outStruct.Headers = nil
|
||||
}
|
||||
|
||||
outData, err := json.Marshal(outStruct)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return NewAllocation(BytesToLeakedPtr(outData))
|
||||
}
|
||||
|
||||
func VerifyChallengeDecode(callback func(in VerifyChallengeInput) VerifyChallengeOutput, in Allocation) (out VerifyChallengeOutput) {
|
||||
var inStruct VerifyChallengeInput
|
||||
|
||||
inData := PtrToBytes(in.Pointer(), in.Size())
|
||||
|
||||
err := json.Unmarshal(inData, &inStruct)
|
||||
if err != nil {
|
||||
return VerifyChallengeOutputError
|
||||
} else {
|
||||
func() {
|
||||
// encapsulate err
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
out = VerifyChallengeOutputError
|
||||
}
|
||||
}()
|
||||
out = callback(inStruct)
|
||||
}()
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
type MakeChallengeInput struct {
|
||||
Key []byte `json:"key"`
|
||||
|
||||
Parameters map[string]string `json:"parameters,omitempty"`
|
||||
|
||||
Headers http.Header `json:"headers,omitempty"`
|
||||
Data []byte `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type MakeChallengeOutput struct {
|
||||
Data []byte `json:"data"`
|
||||
Code int `json:"code"`
|
||||
Headers http.Header `json:"headers,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type VerifyChallengeInput struct {
|
||||
Key []byte `json:"key"`
|
||||
Parameters map[string]string `json:"parameters,omitempty"`
|
||||
|
||||
Result []byte `json:"result,omitempty"`
|
||||
}
|
||||
|
||||
type VerifyChallengeOutput uint64
|
||||
|
||||
const (
|
||||
VerifyChallengeOutputOK = VerifyChallengeOutput(iota)
|
||||
VerifyChallengeOutputFailed
|
||||
VerifyChallengeOutputError
|
||||
)
|
||||
1
challenge/js-pow-sha256/runtime/.gitignore
vendored
Normal file
1
challenge/js-pow-sha256/runtime/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.wasm
|
||||
92
challenge/js-pow-sha256/runtime/runtime.go
Normal file
92
challenge/js-pow-sha256/runtime/runtime.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"git.gammaspectra.live/git/go-away/challenge"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:generate tinygo build -target wasip1 -buildmode=c-shared -scheduler=none -gc=leaking -o runtime.wasm runtime.go
|
||||
func main() {
|
||||
|
||||
}
|
||||
|
||||
func getChallenge(key []byte, params map[string]string) ([]byte, uint64) {
|
||||
difficulty := uint64(5)
|
||||
var err error
|
||||
if diffStr, ok := params["difficulty"]; ok {
|
||||
difficulty, err = strconv.ParseUint(diffStr, 10, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
hasher := sha256.New()
|
||||
hasher.Write(binary.LittleEndian.AppendUint64(nil, difficulty))
|
||||
hasher.Write(key)
|
||||
return hasher.Sum(nil), difficulty
|
||||
}
|
||||
|
||||
//go:wasmexport MakeChallenge
|
||||
func MakeChallenge(in challenge.Allocation) (out challenge.Allocation) {
|
||||
return challenge.MakeChallengeDecode(func(in challenge.MakeChallengeInput, out *challenge.MakeChallengeOutput) {
|
||||
type Result struct {
|
||||
Challenge string `json:"challenge"`
|
||||
Difficulty uint64 `json:"difficulty"`
|
||||
}
|
||||
|
||||
challenge, difficulty := getChallenge(in.Key, in.Parameters)
|
||||
|
||||
data, err := json.Marshal(Result{
|
||||
Challenge: hex.EncodeToString(challenge),
|
||||
Difficulty: difficulty,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
out.Data = data
|
||||
out.Headers.Set("Content-Type", "text/javascript; charset=utf-8")
|
||||
}, in)
|
||||
}
|
||||
|
||||
//go:wasmexport VerifyChallenge
|
||||
func VerifyChallenge(in challenge.Allocation) (out challenge.VerifyChallengeOutput) {
|
||||
return challenge.VerifyChallengeDecode(func(in challenge.VerifyChallengeInput) challenge.VerifyChallengeOutput {
|
||||
c, difficulty := getChallenge(in.Key, in.Parameters)
|
||||
|
||||
type Result struct {
|
||||
Hash string `json:"hash"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
}
|
||||
var result Result
|
||||
err := json.Unmarshal(in.Result, &result)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(result.Hash, strings.Repeat("0", int(difficulty))) {
|
||||
return challenge.VerifyChallengeOutputFailed
|
||||
}
|
||||
|
||||
resultBinary, err := hex.DecodeString(result.Hash)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, len(c)+8)
|
||||
buf = append(buf, c[:]...)
|
||||
buf = binary.LittleEndian.AppendUint64(buf, result.Nonce)
|
||||
calculated := sha256.Sum256(buf)
|
||||
|
||||
if subtle.ConstantTimeCompare(resultBinary, calculated[:]) != 1 {
|
||||
return challenge.VerifyChallengeOutputFailed
|
||||
}
|
||||
|
||||
return challenge.VerifyChallengeOutputOK
|
||||
}, in)
|
||||
}
|
||||
125
challenge/js-pow-sha256/static/load.mjs
Normal file
125
challenge/js-pow-sha256/static/load.mjs
Normal file
@@ -0,0 +1,125 @@
|
||||
let _worker;
|
||||
let _webWorkerURL;
|
||||
let _challenge;
|
||||
let _difficulty;
|
||||
|
||||
async function setup(config) {
|
||||
|
||||
const status = document.getElementById('status');
|
||||
const image = document.getElementById('image');
|
||||
const title = document.getElementById('title');
|
||||
const spinner = document.getElementById('spinner');
|
||||
|
||||
const { challenge, difficulty } = await fetch(config.Path + "/make-challenge", { method: "POST" })
|
||||
.then(r => {
|
||||
if (!r.ok) {
|
||||
throw new Error("Failed to fetch config");
|
||||
}
|
||||
return r.json();
|
||||
})
|
||||
.catch(err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
_challenge = challenge;
|
||||
_difficulty = difficulty;
|
||||
|
||||
_webWorkerURL = URL.createObjectURL(new Blob([
|
||||
'(', processTask(challenge, difficulty), ')()'
|
||||
], { type: 'application/javascript' }));
|
||||
_worker = new Worker(_webWorkerURL);
|
||||
|
||||
return `Difficulty ${difficulty}`
|
||||
}
|
||||
|
||||
function challenge() {
|
||||
return new Promise((resolve, reject) => {
|
||||
_worker.onmessage = (event) => {
|
||||
_worker.terminate();
|
||||
resolve(event.data);
|
||||
};
|
||||
|
||||
_worker.onerror = (event) => {
|
||||
_worker.terminate();
|
||||
reject();
|
||||
};
|
||||
|
||||
_worker.postMessage({
|
||||
challenge: _challenge,
|
||||
difficulty: _difficulty,
|
||||
});
|
||||
|
||||
URL.revokeObjectURL(_webWorkerURL);
|
||||
});
|
||||
}
|
||||
|
||||
function processTask() {
|
||||
return function () {
|
||||
|
||||
const decodeHex = (str) => {
|
||||
let result = new Uint8Array(str.length>>1)
|
||||
for (let i = 0; i < str.length; i += 2){
|
||||
result[i>>1] = parseInt(str.substring(i, i + 2), 16)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const encodeHex = (buf) => {
|
||||
return buf.reduce((a, b) => a + b.toString(16).padStart(2, '0'), '')
|
||||
}
|
||||
|
||||
const lessThan = (buf, target) => {
|
||||
for(let i = 0; i < buf.length; ++i){
|
||||
if (buf[i] < target[i]){
|
||||
return true;
|
||||
} else if (buf[i] > target[i]){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const increment = (number) => {
|
||||
for ( let i = 0; i < number.length; i++ ) {
|
||||
if(number[i]===255){
|
||||
number[i] = 0;
|
||||
} else {
|
||||
number[i]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addEventListener('message', async (event) => {
|
||||
let data = decodeHex(event.data.challenge);
|
||||
let target = decodeHex("0".repeat(event.data.difficulty) + "f".repeat(64 - event.data.difficulty));
|
||||
|
||||
let nonce = new Uint8Array(8);
|
||||
let buf = new Uint8Array(data.length + nonce.length);
|
||||
buf.set(data, 0);
|
||||
|
||||
while(true) {
|
||||
buf.set(nonce, data.length);
|
||||
let result = new Uint8Array(await crypto.subtle.digest("SHA-256", buf))
|
||||
|
||||
if (lessThan(result, target)){
|
||||
const nonceNumber = Number(new BigUint64Array(nonce.buffer).at(0))
|
||||
postMessage({
|
||||
result: {
|
||||
hash: encodeHex(result),
|
||||
nonce: nonceNumber,
|
||||
},
|
||||
info: `iterations ${nonceNumber}`,
|
||||
});
|
||||
return
|
||||
}
|
||||
increment(nonce)
|
||||
}
|
||||
|
||||
});
|
||||
}.toString();
|
||||
}
|
||||
|
||||
export { setup, challenge }
|
||||
59
challenge/tinygo.go
Normal file
59
challenge/tinygo.go
Normal file
@@ -0,0 +1,59 @@
|
||||
//go:build tinygo
|
||||
|
||||
package challenge
|
||||
|
||||
// #include <stdlib.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// PtrToBytes returns a byte slice from WebAssembly compatible numeric types
|
||||
// representing its pointer and length.
|
||||
func PtrToBytes(ptr uint32, size uint32) []byte {
|
||||
return unsafe.Slice((*byte)(unsafe.Pointer(uintptr(ptr))), size)
|
||||
}
|
||||
|
||||
// BytesToPtr returns a pointer and size pair for the given byte slice in a way
|
||||
// compatible with WebAssembly numeric types.
|
||||
// The returned pointer aliases the slice hence the slice must be kept alive
|
||||
// until ptr is no longer needed.
|
||||
func BytesToPtr(s []byte) (uint32, uint32) {
|
||||
ptr := unsafe.Pointer(unsafe.SliceData(s))
|
||||
return uint32(uintptr(ptr)), uint32(len(s))
|
||||
}
|
||||
|
||||
// BytesToLeakedPtr returns a pointer and size pair for the given byte slice in a way
|
||||
// compatible with WebAssembly numeric types.
|
||||
// The pointer is not automatically managed by TinyGo hence it must be freed by the host.
|
||||
func BytesToLeakedPtr(s []byte) (uint32, uint32) {
|
||||
size := C.ulong(len(s))
|
||||
ptr := unsafe.Pointer(C.malloc(size))
|
||||
copy(unsafe.Slice((*byte)(ptr), size), s)
|
||||
return uint32(uintptr(ptr)), uint32(size)
|
||||
}
|
||||
|
||||
// PtrToString returns a string from WebAssembly compatible numeric types
|
||||
// representing its pointer and length.
|
||||
func PtrToString(ptr uint32, size uint32) string {
|
||||
return unsafe.String((*byte)(unsafe.Pointer(uintptr(ptr))), size)
|
||||
}
|
||||
|
||||
// StringToPtr returns a pointer and size pair for the given string in a way
|
||||
// compatible with WebAssembly numeric types.
|
||||
// The returned pointer aliases the string hence the string must be kept alive
|
||||
// until ptr is no longer needed.
|
||||
func StringToPtr(s string) (uint32, uint32) {
|
||||
ptr := unsafe.Pointer(unsafe.StringData(s))
|
||||
return uint32(uintptr(ptr)), uint32(len(s))
|
||||
}
|
||||
|
||||
// StringToLeakedPtr returns a pointer and size pair for the given string in a way
|
||||
// compatible with WebAssembly numeric types.
|
||||
// The pointer is not automatically managed by TinyGo hence it must be freed by the host.
|
||||
func StringToLeakedPtr(s string) (uint32, uint32) {
|
||||
size := C.ulong(len(s))
|
||||
ptr := unsafe.Pointer(C.malloc(size))
|
||||
copy(unsafe.Slice((*byte)(ptr), size), s)
|
||||
return uint32(uintptr(ptr)), uint32(size)
|
||||
}
|
||||
Reference in New Issue
Block a user