Public/Split-SecretSharingSecret.ps1

function Split-SecretSharingSecret {
    <#
    .SYNOPSIS
        Splits a secret into SLIP-0039 mnemonic shares under a group/member quorum scheme.
    .DESCRIPTION
        Encrypts Secret (optionally with a Passphrase) into an encrypted master secret,
        splits it across one or more groups via a GroupThreshold-of-Group.Count Shamir
        scheme, and splits each group's share across that group's members via its own
        Threshold-of-Count scheme. Returns one object per member share.

        For the common case - a single group, T-of-N - pass one -Group entry and leave
        -GroupThreshold at its default of 1: e.g. -Group @{ Threshold = 3; Count = 5 }
        for a classic 3-of-5 split. Multi-group hierarchical schemes are supported by
        passing more than one -Group entry and a -GroupThreshold greater than 1.
    .PARAMETER Secret
        The secret to split, e.g. from New-SecretSharingSecret.
    .PARAMETER Passphrase
        An optional passphrase. An empty/omitted passphrase is a valid, deliberate
        SLIP-0039 input - see the .NOTES on Join-SecretSharingSecret for why a wrong
        passphrase cannot be detected.
    .PARAMETER Group
        One hashtable per group, each with Threshold and Count keys, e.g.
        @{ Threshold = 3; Count = 5 }. At least 1, at most 16 entries.
    .PARAMETER GroupThreshold
        The number of groups that must each meet their own member threshold to
        reconstruct the secret. Defaults to 1 (the whole secret needs only one group,
        the common single-group case).
    .PARAMETER Extendable
        Marks the backup as extendable: shares can be added to the set later without
        invalidating existing ones, at the cost of the encryption salt not being bound
        to this specific share set.
    .PARAMETER IterationExponent
        Controls the PBKDF2 iteration count for the master-secret cipher
        (10000 << IterationExponent, divided across 4 rounds). 0-15, default 0.
    .EXAMPLE
        PS> $secret = New-SecretSharingSecret -Entropy 128
        PS> Split-SecretSharingSecret -Secret $secret -Group @{ Threshold = 3; Count = 5 }
        Splits a 128-bit secret into 5 shares, any 3 of which reconstruct it.
    .OUTPUTS
        PSCustomObject, one per member share, with Identifier, Extendable,
        IterationExponent, GroupIndex, GroupThreshold, GroupCount, MemberIndex,
        MemberThreshold, and Mnemonic properties.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)]
        [System.Security.SecureString]$Secret,

        [System.Security.SecureString]$Passphrase,

        [Parameter(Mandatory)]
        [ValidateCount(1, 16)]
        [Hashtable[]]$Group,

        [ValidateRange(1, 16)]
        [int]$GroupThreshold = 1,

        [switch]$Extendable,

        [ValidateRange(0, 15)]
        [int]$IterationExponent = 0
    )

    if ($GroupThreshold -gt $Group.Count) {
        throw "GroupThreshold ($GroupThreshold) cannot exceed the number of -Group entries supplied ($($Group.Count))."
    }

    $groupSpec = @(foreach ($g in $Group) {
        if (-not ($g.ContainsKey('Threshold') -and $g.ContainsKey('Count'))) {
            throw "Each -Group entry must be a hashtable with 'Threshold' and 'Count' keys."
        }

        $threshold = [int]$g['Threshold']
        $count = [int]$g['Count']

        if ($threshold -lt 1 -or $threshold -gt 16) {
            throw "Group Threshold must be between 1 and 16, got $threshold."
        }
        if ($count -lt 1 -or $count -gt 16) {
            throw "Group Count must be between 1 and 16, got $count."
        }
        if ($threshold -gt $count) {
            throw "Group Threshold ($threshold) cannot exceed Group Count ($count)."
        }

        [PSCustomObject]@{ Threshold = $threshold; Count = $count }
    })

    $secretByte = ConvertFrom-SecretSharingSecureString -SecureString $Secret
    $passphraseByte = [byte[]]@()
    if ($Passphrase) {
        $passphraseByte = ConvertFrom-SecretSharingPassphraseSecureString -SecureString $Passphrase
    }

    $digestConstant = Get-SecretSharingDigestConstant
    $identifier = Get-SecretSharingRandomIdentifier

    $encryptedMasterSecret = Protect-SecretSharingMasterSecret -MasterSecret $secretByte -Passphrase $passphraseByte `
        -Identifier $identifier -IterationExponent $IterationExponent -Extendable:$Extendable

    $groupXCoordinate = [byte[]](0..($groupSpec.Count - 1))
    if ($GroupThreshold -eq 1) {
        $groupPoint = Split-SecretSharingShamirSecret -Secret $encryptedMasterSecret -SecretXCoordinate $digestConstant.SecretIndex `
            -Threshold 1 -XCoordinate $groupXCoordinate
    } else {
        $groupDigest = New-SecretSharingDigestShare -Secret $encryptedMasterSecret
        $groupFixedPoint = @([PSCustomObject]@{ X = $digestConstant.DigestIndex; Value = $groupDigest })
        $groupPoint = Split-SecretSharingShamirSecret -Secret $encryptedMasterSecret -SecretXCoordinate $digestConstant.SecretIndex `
            -Threshold $GroupThreshold -XCoordinate $groupXCoordinate -FixedPoint $groupFixedPoint
    }

    for ($groupIndex = 0; $groupIndex -lt $groupSpec.Count; $groupIndex++) {
        $spec = $groupSpec[$groupIndex]
        $groupShareValue = ($groupPoint | Where-Object { $_.X -eq $groupIndex }).Value

        $memberXCoordinate = [byte[]](0..($spec.Count - 1))
        if ($spec.Threshold -eq 1) {
            $memberPoint = Split-SecretSharingShamirSecret -Secret $groupShareValue -SecretXCoordinate $digestConstant.SecretIndex `
                -Threshold 1 -XCoordinate $memberXCoordinate
        } else {
            $memberDigest = New-SecretSharingDigestShare -Secret $groupShareValue
            $memberFixedPoint = @([PSCustomObject]@{ X = $digestConstant.DigestIndex; Value = $memberDigest })
            $memberPoint = Split-SecretSharingShamirSecret -Secret $groupShareValue -SecretXCoordinate $digestConstant.SecretIndex `
                -Threshold $spec.Threshold -XCoordinate $memberXCoordinate -FixedPoint $memberFixedPoint
        }

        foreach ($member in $memberPoint) {
            $dataWord = ConvertTo-SecretSharingShareWord -Identifier $identifier -Extendable:$Extendable `
                -IterationExponent $IterationExponent -GroupIndex $groupIndex -GroupThreshold $GroupThreshold `
                -GroupCount $groupSpec.Count -MemberIndex ([int]$member.X) -MemberThreshold $spec.Threshold -Value $member.Value

            $checksum = New-SecretSharingChecksum -Data $dataWord -Extendable:$Extendable
            $fullWord = [int[]]($dataWord + $checksum)
            $mnemonic = ($fullWord | ForEach-Object { ConvertTo-SecretSharingMnemonicWord -Index $_ }) -join ' '

            [PSCustomObject]@{
                Identifier        = $identifier
                Extendable        = [bool]$Extendable
                IterationExponent = $IterationExponent
                GroupIndex        = $groupIndex
                GroupThreshold    = $GroupThreshold
                GroupCount        = $groupSpec.Count
                MemberIndex       = [int]$member.X
                MemberThreshold   = $spec.Threshold
                Mnemonic          = $mnemonic
            }
        }
    }
}