Tests/Private/Shamir.Tests.ps1

BeforeDiscovery {
    Import-Module (Join-Path $PSScriptRoot '../../Posh-SecretSharing.psd1') -Force
}

Describe 'Private/Shamir' -Tag Unit {
    InModuleScope 'Posh-SecretSharing' {
        Describe 'Get-SecretSharingRandomByte' {
            It 'returns the requested number of bytes' {
                (Get-SecretSharingRandomByte -Length 16).Count | Should -Be 16
            }

            It 'does not return the same bytes on two successive calls' {
                $a = Get-SecretSharingRandomByte -Length 32
                $b = Get-SecretSharingRandomByte -Length 32
                (Compare-Object $a $b) | Should -Not -BeNullOrEmpty
            }
        }

        Describe 'Invoke-SecretSharingShamirInterpolation' {
            It 'returns the matching point directly when X is already known' {
                $points = @(
                    [PSCustomObject]@{ X = [byte]1; Value = [byte[]](10, 20, 30) }
                    [PSCustomObject]@{ X = [byte]2; Value = [byte[]](40, 50, 60) }
                )
                $actual = Invoke-SecretSharingShamirInterpolation -Point $points -X 1
                $actual | Should -Be ([byte[]](10, 20, 30))
            }

            It 'reconstructs the constant term of a known degree-1 polynomial from any 2 of its points' {
                $secret = [byte[]](0xAB, 0x11)
                $coefficient = [byte[]](0x07, 0x9A)

                $points = 1..3 | ForEach-Object {
                    $x = [byte]$_
                    $value = for ($i = 0; $i -lt $secret.Length; $i++) {
                        $term = Invoke-SecretSharingFieldMultiplication -A $coefficient[$i] -B $x
                        Invoke-SecretSharingFieldAddition -A $secret[$i] -B $term
                    }
                    [PSCustomObject]@{ X = $x; Value = [byte[]]$value }
                }

                $actual1 = Invoke-SecretSharingShamirInterpolation -Point $points[0, 1] -X 0
                $actual1 | Should -Be $secret
                $actual2 = Invoke-SecretSharingShamirInterpolation -Point $points[1, 2] -X 0
                $actual2 | Should -Be $secret
            }

            It 'throws on duplicate X-coordinates' {
                $points = @(
                    [PSCustomObject]@{ X = [byte]5; Value = [byte[]](1, 2) }
                    [PSCustomObject]@{ X = [byte]5; Value = [byte[]](3, 4) }
                )
                { Invoke-SecretSharingShamirInterpolation -Point $points -X 0 } | Should -Throw
            }

            It 'throws when point values have inconsistent lengths' {
                $points = @(
                    [PSCustomObject]@{ X = [byte]1; Value = [byte[]](1, 2) }
                    [PSCustomObject]@{ X = [byte]2; Value = [byte[]](3, 4, 5) }
                )
                { Invoke-SecretSharingShamirInterpolation -Point $points -X 0 } | Should -Throw
            }
        }

        Describe 'Split-SecretSharingShamirSecret' {
            It 'returns the secret itself for every share when Threshold is 1' {
                $secret = [byte[]](1, 2, 3, 4)
                $shares = Split-SecretSharingShamirSecret -Secret $secret -SecretXCoordinate 255 -Threshold 1 -XCoordinate ([byte[]](0, 1, 2))
                foreach ($share in $shares) {
                    $share.Value | Should -Be $secret
                }
            }

            It 'reconstructs the original secret from any Threshold of the returned shares' {
                $secret = Get-SecretSharingRandomByte -Length 8
                $shares = Split-SecretSharingShamirSecret -Secret $secret -SecretXCoordinate 255 -Threshold 3 -XCoordinate ([byte[]](0, 1, 2, 3, 4))

                $subset = $shares[1, 3, 4] | ForEach-Object { [PSCustomObject]@{ X = $_.X; Value = $_.Value } }
                $actual = Invoke-SecretSharingShamirInterpolation -Point $subset -X 255
                $actual | Should -Be $secret
            }

            It 'produces different shares for the same secret on repeated calls' {
                $secret = [byte[]](9, 9, 9, 9)
                $sharesA = Split-SecretSharingShamirSecret -Secret $secret -SecretXCoordinate 255 -Threshold 3 -XCoordinate ([byte[]](0, 1, 2))
                $sharesB = Split-SecretSharingShamirSecret -Secret $secret -SecretXCoordinate 255 -Threshold 3 -XCoordinate ([byte[]](0, 1, 2))
                ($sharesA[0].Value -join ',') | Should -Not -Be ($sharesB[0].Value -join ',')
            }

            It 'incorporates a supplied FixedPoint anchor into the reconstructed polynomial' {
                $secret = [byte[]](5, 6, 7, 8)
                $fixedPoint = @([PSCustomObject]@{ X = [byte]254; Value = (Get-SecretSharingRandomByte -Length 4) })

                $shares = Split-SecretSharingShamirSecret -Secret $secret -SecretXCoordinate 255 -Threshold 3 -XCoordinate ([byte[]](0, 1, 2, 3)) -FixedPoint $fixedPoint

                $subset = @($shares[0], $shares[1]) + $fixedPoint
                $actual = Invoke-SecretSharingShamirInterpolation -Point $subset -X 255
                $actual | Should -Be $secret
            }

            It 'throws when there are not enough XCoordinate slots for the required random shares' {
                { Split-SecretSharingShamirSecret -Secret ([byte[]](1, 2)) -SecretXCoordinate 255 -Threshold 5 -XCoordinate ([byte[]](0, 1)) } | Should -Throw
            }

            It 'throws when a threshold of 1 is combined with a FixedPoint anchor' {
                $fixedPoint = @([PSCustomObject]@{ X = [byte]254; Value = [byte[]](1, 2) })
                { Split-SecretSharingShamirSecret -Secret ([byte[]](1, 2)) -SecretXCoordinate 255 -Threshold 1 -XCoordinate ([byte[]](0)) -FixedPoint $fixedPoint } | Should -Throw
            }
        }
    }
}