Public/Resolve-ShamirSecretShares.ps1

<#
.SYNOPSIS
    Recovers a secret from Shamir's Secret Sharing scheme shares.

.DESCRIPTION
    This function takes an array of Base64 encoded share objects and recovers the original secret string using Shamir's Secret Sharing. It deserializes the shares, extracts individual share information, sorts shares by chunk index, recovers each chunk secret, and finally combines the recovered chunks back into the original secret.

.PARAMETER Shares
    An array of strings containing Base64 encoded share objects, generated by the `Get-ShamirSecretShares` function.

.OUTPUTS
    The recovered secret as a byte array. You can further convert it to a string using `ConvertFrom-Bytes`.

.EXAMPLE
    Recover a secret from shares:
    Resolve-ShamirSecretShares -Shares $Shares
#>

Function Resolve-ShamirSecretShares {
    Param(
        [Parameter(Mandatory)]
        [Array]$Shares
    )

    # Check if we got passed the shares directly from Get-ShamirSecretShares and extract the share property
    If( $Shares[0].GetType().Name -eq 'PSCustomObject' ) {
        # Check if we have the right property
        If(-not (($Shares | Get-Member -MemberType NoteProperty).Name -contains 'Share')) {
            throw "Passed object does not have a share property. Please either pass the given object of Get-ShamirSecretShares or pass an array of the serialized shares."
        }

        $Shares = $Shares.Share
    }

    # Retrieve the actual encrypted data from the base64 string
    Try {
        $UnserializedData = $Shares | ConvertFrom-SerializedObject
    } Catch {
        Throw [System.Management.Automation.ParsingMetadataException] "Could not unserialize share data"
    }

    # Make sure the unserialized data has all required fields
    'Id', 'Data' | Foreach-Object {
        $RequiredProperty = $_
        $Members = ($UnserializedData | Get-Member -MemberType NoteProperty).Name

        If($Members -notcontains $RequiredProperty) {
            throw "Passed object does not contain required property $RequiredProperty"
        }
    }

    # Unzip the shares
    $AllShares = Foreach($SharePackageById in $UnserializedData) {
        $ShareId = $SharePackageById.Id

        Foreach($Share in $SharePackageById.Data) {
            # Verify that the share has all required properties
            'chunkindex', 'value' | Foreach-Object {
                $RequiredProperty = $_
                $Members = ($Share | Get-Member -MemberType NoteProperty).Name

                If($Members -notcontains $RequiredProperty) {
                    throw "Share $ShareId does not contain required property $RequiredProperty"
                }
            }

            [PSCustomObject]@{
                id = $ShareId
                value = $Share.value
                chunkindex = $Share.chunkindex
            }
        }
    }

    $SortedChunks = $UnserializedData.Data.chunkindex | Select-Object -Unique | Sort-Object
    $RecoveredChunks = Foreach($ChunkIndex in $SortedChunks) {
        # Get all shares for the current data chunk
        $ChunkShares = $AllShares | Where-Object chunkindex -eq $ChunkIndex

        $ExpectedShares = $ChunkShares | Select-Object @{
            Name = 'id'
            Expr = { $_.id }
        }, @{
            Name = 'value'
            Expr = { $_.value }
        }

        [String]$Secret = Resolve-Secret $ExpectedShares

        # Pad the resolved secret to accomodate for lost leading zeros due to the type conversion
        If(($Secret.Length % 3) -ne 0) {
            $ExpectedSize = $Secret.Length + (3 - ($Secret.Length % 3))
            $Secret = $Secret.PadLeft($ExpectedSize, '0')
        }

        Write-Output $Secret
    }

    # Join the resolved secrets and split the resulting string to chunks of 3 chars
    Try {
        # TODO: Find a better way to handle supposedly wrong or few shares
        [Byte[]]$Bytes = Split-Array -Array ($RecoveredChunks -join '').ToCharArray() -ChunkSize 3 | Foreach-Object { $_ -join '' }
    } Catch {
        throw "Could not resolve secret. Did you provide the correct amount of the correct shares?"
    }

    $Bytes | ConvertFrom-Bytes
}