moon_shamir

    Threshold secret sharing over GF(256): deterministic k-of-n key splitting with verifiable recovery

    secret-sharing
    shamir
    threshold-cryptography
    backup
    gf256
    Download zip
    Version
    0.2.0
    License
    Apache-2.0
    Last updated
    20 hours ago
    Downloads
    12

    No dependencies

    #Moon Shamir

    Threshold secret sharing over GF(256): split a secret into n shares of which any k recover it, in pure MoonBit with a JSON CLI and reusable library API.

    #Why

    A key that exists in one place is one accident away from being gone, and one leak away from being everyone's. Shamir's scheme turns a secret into n shares with an information-theoretic guarantee: any k shares determine it exactly, and any k-1 leave every possible secret equally likely - not computationally hard to distinguish, indistinguishable. Lose shares, keep the secret; steal shares below the threshold, learn nothing.

    #What it does

    Each byte of the secret becomes the constant term of its own random polynomial of degree k-1 over GF(2^8) (the AES polynomial 0x11B); share i is the evaluation of every polynomial at x = i. Combine interpolates the constant terms back from any k shares.

    • split: secret as hex, threshold 2..255, shares 2..255, and a seed. The seed is the entropy of the split - the randomness source is injected by the caller, never read from the environment, and the same (secret, seed) pair reproduces the same shares bit for bit on every backend. Production callers inject a high-entropy seed; SplitMix64 is deterministic by design and is not a security primitive.
    • combine: k or more shares. Below the threshold the door stays closed
      • there is no wrong-secret output to misuse. Every share carries a CRC-32 label of the secret, so a corrupted or foreign share is caught as verified: false; the label is an integrity check, explicitly not a MAC, and the secrecy rests only on the information-theoretic argument.

    {"operation": "split", "secret_hex": "0011223344556677", "threshold": 3, "shares": 5, "seed": 20261104}

    Two audit operations sit beside them. diagnose takes a share set, enumerates every k-sized subset (bounded: at most 12 shares, threshold at most 6), and names the shares that disagree with every quorum - a corrupted share is identified, not merely suspected. verify performs the recovery and reports a verdict without returning the secret; with an optional secret_hex it also proves the set belongs to that exact secret. The CLI wraps it all:

    node scripts/shamir.mjs split 0011223344556677 3 5 20261104 node scripts/shamir.mjs combine 1:3:8407759b:89ecf4f6... 3:3:...:... node scripts/shamir.mjs diagnose <shares...> node scripts/shamir.mjs verify <shares...> [secret-hex]

    #Verification

    The Python oracle independently rebuilds the scheme from exponent/log tables - a different construction path than the implementation's shift-and-reduce multiplier - including its own SplitMix64, then checks 40 random splits share-for-share, recovers the secret from every k-sized subset of the share set (all subsets while n is small, sampled ones beyond), pins determinism and seed sensitivity, and walks malformed requests. The algebraic law - every C(n,k) subset of a k-of-n split recovers the same secret - is asserted exhaustively in white-box tests too. Four backends (js, wasm, wasm-gc, native) build with zero warnings.

    moon build --target js sh scripts/verify.sh node _build/js/debug/build/cmd/main/main.js examples/split.json

    #Library use

    execute(Json) -> Json raise is the standard JSON boundary; run(Value)-> Value raise is the typed one. Every exported function raises InputError with a plain message.

    #Boundaries

    Secrets are hex, capped at 16384 bytes; share x coordinates are 1..255, so n is at most 255. Seeds travel as JSON numbers and are capped at 2^53-1, the last integer a double carries exactly. The CRC-32 label detects corruption; it does not authenticate. SplitMix64 makes splits reproducible from the seed - treat the seed as the secret-bearing entropy and discard nothing you would not publish about the secret itself.

    InputError

    pub(all) suberror InputError {
    InputError(String)
    } derive(Eq,
    Debug
    )

    impl Show for InputError

    InputError::equal

    fn InputError::equal(InputError, InputError) -> Bool

    InputError::not_equal

    fn InputError::not_equal(x : InputError, y : InputError) -> Bool

    InputError::output

    fn InputError::output(self : InputError, logger : &Logger) -> Unit

    InputError::to_string

    fn InputError::to_string(self : InputError) -> String

    Value

    pub(all) enum Value {
    Null
    Bool(Bool)
    Number(Double)
    String(String)
    Array(Array[Value])
    Object(Map[String, Value])
    } derive(Eq,
    Debug
    )

    A JSON-shaped semantic value. Requests and reports travel as these; object key order is never significant, and numbers are finite doubles.

    Value::equal

    fn Value::equal(Value, Value) -> Bool

    Value::not_equal

    fn Value::not_equal(x : Value, y : Value) -> Bool

    Value::to_json

    Value::to_repr

    arr

    fn arr(value : Value) -> Array[Value] raise InputError

    execute

    The standard JSON boundary: one request object in, one report object out. Failures raise InputError with a plain message.

    fields_only

    fn fields_only(value : Value, allowed : Array[String]) -> Unit raise InputError

    from_json

    fn from_json(value :
    Json
    ) -> Value

    get

    fn get(value : Value, key : String) -> Value

    has

    fn has(value : Value, key : String) -> Bool

    num

    fn num(value : Value) -> Double raise InputError

    record

    fn record(fields : Array[(String, Value)]) -> Value

    required

    fn required(value : Value, key : String) -> Value raise InputError

    run

    fn run(request : Value) -> Value raise InputError

    The typed boundary over the same requests.

    str

    fn str(value : Value) -> String raise InputError