Skip to content

Commit

Permalink
Merge pull request #1 from lucasmenendez/dev
Browse files Browse the repository at this point in the history
v0.0.3: web demo
  • Loading branch information
lucasmenendez authored Apr 7, 2024
2 parents c0e333b + 1541860 commit f36596a
Show file tree
Hide file tree
Showing 5 changed files with 315 additions and 0 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Release new version

on:
push:
tags:
- '*'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false

jobs:
publish:
runs-on: ubuntu-latest
env:
GOOS: js
GOARCH: wasm
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: 1.22.x
cache: true
- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
- name: Build wasm
run: |
go build -o ${{ steps.pages.outputs.base_path }}/gosss.wasm ./cmd/webassembly/main.go
- name: Copy go wasm js engine
run: |
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ${{ steps.pages.outputs.base_path }}
cp -r ./web ${{ steps.pages.outputs.base_path }}
- name: Upload to Pages
uses: actions/upload-pages-artifact@v3
with:
path: ${{ steps.pages.outputs.base_path }}
- name: Deploy to Pages
id: deployment
uses: actions/deploy-pages@v4
95 changes: 95 additions & 0 deletions cmd/webassembly/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//go:build js && wasm
// +build js,wasm

package main

import (
"encoding/json"
"fmt"
"syscall/js"

"github.com/lucasmenendez/gosss"
)

const (
// js names
jsClassName = "GoSSS"
jsHideMethod = "hide"
jsRecoverMethod = "recover"
jsLimitsMethod = "limits"
// required number of arguments for each method
hidedNArgs = 3 // msg, nshares, minshares
recoverdNArgs = 1 // shares
limitsNArgs = 1 // msg
)

func wasmResult(data interface{}, err error) js.Value {
response := map[string]interface{}{}
if data != nil {
response["data"] = data
}
if err != nil {
response["error"] = err.Error()
}
result, err := json.Marshal(response)
if err != nil {
return js.ValueOf(fmt.Sprintf(`{"error": "%s"}`, err.Error()))
}
return js.ValueOf(string(result))
}

func main() {
gosssClass := js.ValueOf(map[string]interface{}{})
gosssClass.Set(jsHideMethod, js.FuncOf(func(this js.Value, p []js.Value) interface{} {
if len(p) != hidedNArgs {
return wasmResult(nil, fmt.Errorf("invalid number of arguments"))
}
msg := p[0].String()
nshares := p[1].Int()
minshares := p[2].Int()
// hide the message
shares, err := gosss.HideMessage([]byte(msg), &gosss.Config{
Shares: nshares,
Min: minshares,
})
if err != nil {
return wasmResult(nil, err)
}
return wasmResult(shares, nil)
}))

gosssClass.Set(jsRecoverMethod, js.FuncOf(func(this js.Value, p []js.Value) interface{} {
if len(p) != recoverdNArgs {
return wasmResult(nil, fmt.Errorf("invalid number of arguments"))
}
// recover the shares from the json string
var shares []string
if err := json.Unmarshal([]byte(p[0].String()), &shares); err != nil {
return wasmResult(nil, err)
}
// recover the message
msg, err := gosss.RecoverMessage(shares, nil)
if err != nil {
return wasmResult(nil, err)
}
return wasmResult(msg, nil)
}))

gosssClass.Set(jsLimitsMethod, js.FuncOf(func(this js.Value, p []js.Value) interface{} {
if len(p) != limitsNArgs {
return wasmResult(nil, fmt.Errorf("invalid number of arguments"))
}
// recover the message
minShares, maxShares, minMin, maxMin := gosss.ConfigLimits([]byte(p[0].String()))
return wasmResult(map[string]int{
"minShares": minShares,
"maxShares": maxShares,
"minMin": minMin,
"maxMin": maxMin,
}, nil)
}))

js.Global().Set(jsClassName, gosssClass)
// keep the program running forever
select {}
}
17 changes: 17 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,23 @@ var DefaultPrime, _ = new(big.Int).SetString("2188824287183927522224640574525727
// using 2 bytes (255^2)
const maxShares = 65536

// ConfigLimits returns the limits for the number of shares and minimum shares
// to recover the original message based on the message size. It returns the
// minimum number of shares, the maximum number of shares, the minimum number
// of shares to recover the secret, and the maximum number of shares to recover
// the secret. The message is divided into parts based on the size of the prime
// number used as finite field, the number of parts is used to calculate the
// limits.
func ConfigLimits(message []byte) (int, int, int, int) {
c := &Config{Prime: DefaultPrime}
secrets := encodeMessage(message, c.maxSecretPartSize())
nSecrets := len(secrets)
minShares := nSecrets * 3
minMin := nSecrets * 2
maxMin := maxShares - nSecrets
return minShares, maxShares, minMin, maxMin
}

// Config struct defines the configuration for the Shamir Secret Sharing
// algorithm. It includes the number of shares to generate, the minimum number
// of shares to recover the secret, and the prime number to use as finite field.
Expand Down
124 changes: 124 additions & 0 deletions web/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.prod.js'

const app = createApp({
data() {
return {
config: {
minShares: 0,
maxShares: 0,
minMin: 0,
maxMin: 0,
},
message: "",
sharesCount: 0,
threshold: 0,
shares: "",
currentTab: "hide",
hide_result: "",
recovered_message: "",
}
},
async created() {
await this.setupWebAssembly();
},
template: `
<div style="width: 90%; max-width: 800px; margin: 50px auto;" class="is-shadowed is-rounded has-p-12">
<header>
<h1 class="title">Shamir's Secret Sharing Demo</h1>
<p>Welcome to the Shamir's Secret Sharing demo 👋.</p>
<p>This tool allows you to securely <b>share a secret message by dividing it</b> into parts. A <b>certain number</b> of parts (threshold) <b>are needed to recover</b> the original message. </p>
<p>This ensures that the secret can only be reconstructed when a sufficient number of parts are brought together.</p>
</header>
<div class="is-flex has-direction-row has-text-center">
<button class="button has-m-2 has-w-full" :class="{'is-normal': currentTab != 'hide'}" @click="currentTab = 'hide'">Hide</button>
<button class="button has-m-2 has-w-full" :class="{'is-normal': currentTab != 'recover'}" @click="currentTab = 'recover'">Recover</button>
</div>
<div v-show="currentTab === 'hide'">
<h3 class="has-mt-6 has-mb-8">Hide a message</h3>
<textarea class="textarea has-mb-4" v-model="message" placeholder="Enter your message" rows="6"></textarea>
<div class="is-flex has-direction-row has-justify-center has-mb-4">
<div class="has-w-full has-m-2" v-show="message">
<label class="label">Shares </label>
<input v-model="sharesCount" type="number" class="input" :min="config.minShares" :max="config.maxShares" :step="config.minShares">
<small>(min {{ config.minShares }}, max {{ config.maxShares }})</small>
</div>
<div class="has-w-full has-m-2" v-show="message">
<label class="label">Threshold</label>
<input v-model="threshold" type="number" class="input" :min="config.minMin" :max="config.maxMin">
<small>(min {{ config.minMin }}, max {{ config.maxMin }})</small>
</div>
</div>
<button class="button is-secondary has-w-full has-mt-4 has-mb-4" :class="{'is-disabled': !message}" @click="hideMessage">Hide</button>
<div class="has-mt-4 has-mb-4" v-show="hide_result">
<h4 class="has-mt-4 has-mb-6">Resulting secret parts</h4>
<textarea class="textarea" readonly v-model="hide_result" placeholder="Resulting secret parts" rows="6"></textarea>
</div>
</div>
<div v-show="currentTab === 'recover'">
<h3 class="has-mt-6 has-mb-8">Recover a message</h3>
<textarea class="textarea has-mb-4" v-model="shares" placeholder="Enter shares" rows="6"></textarea>
<button class="button is-secondary has-w-full has-mt-4 has-mb-4" :class="{'is-disabled': !shares}" @click="recoverMessage">Recover</button>
<div class="has-mt-4 has-mb-4" v-show="recovered_message">
<h4 class="has-mt-4 has-mb-6">Recovered message</h4>
<textarea class="textarea" readonly v-model="recovered_message" placeholder="Recovered message" rows="6"></textarea>
</div>
</div>
</div>
`,
methods: {
async setupWebAssembly() {
const go = new Go();
const result = await WebAssembly.instantiateStreaming(fetch("gosss.wasm"), go.importObject);
go.run(result.instance);
},
getConfig() {
const rawResult = GoSSS.limits(this.message);
const result = JSON.parse(rawResult);
if (!result.error) {
this.config = {
minShares: result.data.minShares,
maxShares: result.data.maxShares,
minMin: result.data.minMin,
maxMin: result.data.maxMin,
}
if (this.sharesCount < this.config.minShares) this.sharesCount = this.config.minShares;
if (this.sharesCount > this.config.maxShares) this.sharesCount = this.config.maxShares;
this.config.maxMin = this.sharesCount - (this.config.minShares / 3);
if (this.threshold < this.config.minMin) this.threshold = this.config.minMin;
if (this.threshold > this.config.maxMin) this.threshold = this.config.maxMin;
} else {
alert(`Error getting configuration: ${result.error}`);
}
},
hideMessage() {
const rawResult = GoSSS.hide(this.message, this.sharesCount, this.threshold);
const result = JSON.parse(rawResult);
if (!result.error) {
this.hide_result = result.data.join("\n");
} else {
alert(`Error hiding message: ${result.error}`);
}
},
recoverMessage() {
const shares = JSON.stringify(this.shares.split("\n"));
const rawResult = GoSSS.recover(shares);
const result = JSON.parse(rawResult);
if (!result.error) {
this.recovered_message = window.atob(result.data);
} else {
alert(`Error recovering message: ${result.error}`);
}
}
},
watch: {
message() {
this.getConfig();
},
sharesCount() {
this.getConfig();
},
},
});

app.mount('#app');
29 changes: 29 additions & 0 deletions web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html data-theme="light">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Title and favicon (SVG emoji)-->
<title>Shamir Secret Sharing Demo</title>
<link rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🔐</text></svg>">
<!-- Siimple.css assets -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/siimple/siimple.css" />
<style>
button.is-normal {
background: #ccc;
color: #222;
}
</style>
</head>
<body>
<!-- App slot -->
<div id="app"></div>
<!-- Import Go WebAssembly engine -->
<script src="wasm_exec.js"></script>
<!-- Import app -->
<script type="module" src="app.js"></script>
</body>
</html>

0 comments on commit f36596a

Please sign in to comment.