Private/EdwardsCurve.psm1
|
#!/usr/bin/env pwsh using namespace System.Security using namespace System.Security.Cryptography using module ./KeypairGen.psm1 using module ./Sha.psm1 #EdwardsCurveSignatures class Ed25519Impl { static hidden [long[]] $GfDConst = @(0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203) static hidden [long[]] $GfD2Const = @(0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406) static hidden [long[]] $GfXConst = @(0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169) static hidden [long[]] $GfYConst = @(0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666) static hidden [long[]] $GfIConst = @(0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83) # group order L (little-endian) static hidden [byte[]] $L = @( 0xED, 0xD3, 0xF5, 0x5C, 0x1A, 0x63, 0x12, 0x58, 0xD6, 0x9C, 0xF7, 0xA2, 0xDE, 0xF9, 0xDE, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 ) static hidden [long[]] Gf0() { return [long[]]::new(16) } static hidden [long[]] Gf1() { $g = [long[]]::new(16); $g[0] = 1; return $g } static hidden [long[]] GfD() { return [long[]]([Ed25519Impl]::GfDConst.Clone()) } static hidden [long[]] GfD2() { return [long[]]([Ed25519Impl]::GfD2Const.Clone()) } static hidden [long[]] GfX() { return [long[]]([Ed25519Impl]::GfXConst.Clone()) } static hidden [long[]] GfY() { return [long[]]([Ed25519Impl]::GfYConst.Clone()) } static hidden [long[]] GfI() { return [long[]]([Ed25519Impl]::GfIConst.Clone()) } static hidden [void] Set25519([long[]]$r, [long[]]$a) { for ($i = 0; $i -lt 16; $i++) { $r[$i] = $a[$i] } } static hidden [void] Car25519([long[]]$o) { for ($i = 0; $i -lt 16; $i++) { $o[$i] += (1L -shl 16) [long]$c = $o[$i] -shr 16 $next = if ($i -lt 15) { $i + 1 } else { 0 } $extra = if ($i -eq 15) { 37L * ($c - 1L) } else { 0L } $o[$next] += $c - 1L + $extra $o[$i] -= $c -shl 16 } } static hidden [void] Sel25519([long[]]$p, [long[]]$q, [int]$b) { [long]$c = - ($b -band 1L) # 0 or -1 (all-ones mask) for ($i = 0; $i -lt 16; $i++) { [long]$t = $c -band ($p[$i] -bxor $q[$i]) $p[$i] = $p[$i] -bxor $t $q[$i] = $q[$i] -bxor $t } } static hidden [void] Pack25519([byte[]]$o, [long[]]$n) { $m = [long[]]::new(16) $t = [long[]]::new(16) for ($i = 0; $i -lt 16; $i++) { $t[$i] = $n[$i] } [Ed25519Impl]::Car25519($t) [Ed25519Impl]::Car25519($t) [Ed25519Impl]::Car25519($t) for ($j = 0; $j -lt 2; $j++) { $m[0] = $t[0] - 0xFFED for ($i = 1; $i -lt 15; $i++) { $m[$i] = $t[$i] - 0xFFFF - (($m[$i - 1] -shr 16) -band 1L) $m[$i - 1] = $m[$i - 1] -band 0xFFFF } $m[15] = $t[15] - 0x7FFF - (($m[14] -shr 16) -band 1L) [int]$bv = [int](($m[15] -shr 16) -band 1L) $m[14] = $m[14] -band 0xFFFF [Ed25519Impl]::Sel25519($t, $m, 1 - $bv) } for ($i = 0; $i -lt 16; $i++) { $o[2 * $i] = [byte]($t[$i] -band 0xFF) $o[2 * $i + 1] = [byte]($t[$i] -shr 8) } } static hidden [void] Unpack25519([long[]]$o, [byte[]]$n) { for ($i = 0; $i -lt 16; $i++) { $o[$i] = ($n[2 * $i] -band 0xFF) + ([long]($n[2 * $i + 1] -band 0xFF) -shl 8) } $o[15] = $o[15] -band 0x7FFF } # add static hidden [void] A([long[]]$o, [long[]]$a, [long[]]$b) { for ($i = 0; $i -lt 16; $i++) { $o[$i] = $a[$i] + $b[$i] } } # subtract static hidden [void] Z([long[]]$o, [long[]]$a, [long[]]$b) { for ($i = 0; $i -lt 16; $i++) { $o[$i] = $a[$i] - $b[$i] } } # multiply static hidden [void] M([long[]]$o, [long[]]$a, [long[]]$b) { $t = [long[]]::new(31) for ($i = 0; $i -lt 16; $i++) { for ($j = 0; $j -lt 16; $j++) { $t[$i + $j] += $a[$i] * $b[$j] } } for ($i = 0; $i -lt 15; $i++) { $t[$i] += 38L * $t[$i + 16] } for ($i = 0; $i -lt 16; $i++) { $o[$i] = $t[$i] } [Ed25519Impl]::Car25519($o) [Ed25519Impl]::Car25519($o) } # square static hidden [void] S([long[]]$o, [long[]]$a) { [Ed25519Impl]::M($o, $a, $a) } static hidden [void] Inv25519([long[]]$o, [long[]]$inp) { $c = [long[]]::new(16) for ($i = 0; $i -lt 16; $i++) { $c[$i] = $inp[$i] } for ($a = 253; $a -ge 0; $a--) { [Ed25519Impl]::S($c, $c) if ($a -ne 2 -and $a -ne 4) { [Ed25519Impl]::M($c, $c, $inp) } } for ($i = 0; $i -lt 16; $i++) { $o[$i] = $c[$i] } } static hidden [void] Pow2523([long[]]$o, [long[]]$inp) { $c = [long[]]::new(16) for ($i = 0; $i -lt 16; $i++) { $c[$i] = $inp[$i] } for ($a = 250; $a -ge 0; $a--) { [Ed25519Impl]::S($c, $c) if ($a -ne 1) { [Ed25519Impl]::M($c, $c, $inp) } } for ($i = 0; $i -lt 16; $i++) { $o[$i] = $c[$i] } } static hidden [int] Par25519([long[]]$a) { $d = [byte[]]::new(32) [Ed25519Impl]::Pack25519($d, $a) return $d[0] -band 1 } static hidden [void] Pack([byte[]]$r, [long[][]]$p) { $tx = [long[]]::new(16) $ty = [long[]]::new(16) $zi = [long[]]::new(16) [Ed25519Impl]::Inv25519($zi, $p[2]) [Ed25519Impl]::M($tx, $p[0], $zi) [Ed25519Impl]::M($ty, $p[1], $zi) [Ed25519Impl]::Pack25519($r, $ty) $r[31] = $r[31] -bxor [byte]([Ed25519Impl]::Par25519($tx) -shl 7) } static hidden [bool] Unpackneg([long[][]]$r, [byte[]]$p) { $t = [long[]]::new(16) $chk = [long[]]::new(16) $num = [long[]]::new(16) $den = [long[]]::new(16) $den2 = [long[]]::new(16) $den4 = [long[]]::new(16) $den6 = [long[]]::new(16) [Ed25519Impl]::Set25519($r[2], [Ed25519Impl]::Gf1()) [Ed25519Impl]::Unpack25519($r[1], $p) [Ed25519Impl]::S($num, $r[1]) [Ed25519Impl]::M($den, $num, [Ed25519Impl]::GfD()) [Ed25519Impl]::Z($num, $num, $r[2]) [Ed25519Impl]::A($den, $r[2], $den) [Ed25519Impl]::S($den2, $den) [Ed25519Impl]::S($den4, $den2) [Ed25519Impl]::M($den6, $den4, $den2) [Ed25519Impl]::M($t, $den6, $num) [Ed25519Impl]::M($t, $t, $den) [Ed25519Impl]::Pow2523($t, $t) [Ed25519Impl]::M($t, $t, $num) [Ed25519Impl]::M($t, $t, $den) [Ed25519Impl]::M($t, $t, $den) [Ed25519Impl]::M($r[0], $t, $den) [Ed25519Impl]::S($chk, $r[0]) [Ed25519Impl]::M($chk, $chk, $den) if (![Ed25519Impl]::Neq25519($chk, $num)) { [Ed25519Impl]::M($r[0], $r[0], [Ed25519Impl]::GfI()) } [Ed25519Impl]::S($chk, $r[0]) [Ed25519Impl]::M($chk, $chk, $den) if (![Ed25519Impl]::Neq25519($chk, $num)) { return $false } if ([Ed25519Impl]::Par25519($r[0]) -eq ($p[31] -shr 7)) { [Ed25519Impl]::Z($r[0], [Ed25519Impl]::Gf0(), $r[0]) } [Ed25519Impl]::M($r[3], $r[0], $r[1]) return $true } static hidden [bool] Neq25519([long[]]$a, [long[]]$b) { $c = [byte[]]::new(32) $d = [byte[]]::new(32) [Ed25519Impl]::Pack25519($c, $a) [Ed25519Impl]::Pack25519($d, $b) return [Ed25519Impl]::CryptoVerify32($c, $d) } static hidden [void] Add([long[][]]$p, [long[][]]$q) { $a = [long[]]::new(16); $b = [long[]]::new(16) $c = [long[]]::new(16); $d = [long[]]::new(16) $e = [long[]]::new(16); $f = [long[]]::new(16) $g = [long[]]::new(16); $h = [long[]]::new(16) $t = [long[]]::new(16) [Ed25519Impl]::Z($a, $p[1], $p[0]) [Ed25519Impl]::Z($t, $q[1], $q[0]) [Ed25519Impl]::M($a, $a, $t) [Ed25519Impl]::A($b, $p[0], $p[1]) [Ed25519Impl]::A($t, $q[0], $q[1]) [Ed25519Impl]::M($b, $b, $t) [Ed25519Impl]::M($c, $p[3], $q[3]) [Ed25519Impl]::M($c, $c, [Ed25519Impl]::GfD2()) [Ed25519Impl]::M($d, $p[2], $q[2]) [Ed25519Impl]::A($d, $d, $d) [Ed25519Impl]::Z($e, $b, $a) [Ed25519Impl]::Z($f, $d, $c) [Ed25519Impl]::A($g, $d, $c) [Ed25519Impl]::A($h, $b, $a) [Ed25519Impl]::M($p[0], $e, $f) [Ed25519Impl]::M($p[1], $h, $g) [Ed25519Impl]::M($p[2], $g, $f) [Ed25519Impl]::M($p[3], $e, $h) } static hidden [void] Cswap([long[][]]$p, [long[][]]$q, [int]$b) { for ($i = 0; $i -lt 4; $i++) { [Ed25519Impl]::Sel25519($p[$i], $q[$i], $b) } } static hidden [void] Scalarbase([long[][]]$p, [byte[]]$s) { $q = [long[][]]::new(4) for ($i = 0; $i -lt 4; $i++) { $q[$i] = [long[]]::new(16) } [Ed25519Impl]::Set25519($q[0], [Ed25519Impl]::GfX()) [Ed25519Impl]::Set25519($q[1], [Ed25519Impl]::GfY()) [Ed25519Impl]::Set25519($q[2], [Ed25519Impl]::Gf1()) [Ed25519Impl]::M($q[3], [Ed25519Impl]::GfX(), [Ed25519Impl]::GfY()) [Ed25519Impl]::Scalarmult($p, $q, $s) } static hidden [void] Scalarmult([long[][]]$p, [long[][]]$q, [byte[]]$s) { [Ed25519Impl]::Set25519($p[0], [Ed25519Impl]::Gf0()) [Ed25519Impl]::Set25519($p[1], [Ed25519Impl]::Gf1()) [Ed25519Impl]::Set25519($p[2], [Ed25519Impl]::Gf1()) [Ed25519Impl]::Set25519($p[3], [Ed25519Impl]::Gf0()) for ($i = 255; $i -ge 0; $i--) { [int]$b = ($s[$i -shr 3] -shr ($i -band 7)) -band 1 # use -shr 3 not /8 (PS float division!) [Ed25519Impl]::Cswap($p, $q, $b) [Ed25519Impl]::Add($q, $p) [Ed25519Impl]::Add($p, $p) [Ed25519Impl]::Cswap($p, $q, $b) } } # scalar reduction static hidden [void] ModL([byte[]]$r, [int]$roff, [long[]]$x) { $Lc = [Ed25519Impl]::L for ($i = 63; $i -ge 32; $i--) { [long]$carry = 0L for ($j = $i - 32; $j -lt $i - 12; $j++) { $x[$j] += $carry - 16L * $x[$i] * $Lc[$j - ($i - 32)] $carry = ($x[$j] + 128L) -shr 8 $x[$j] -= $carry -shl 8 } $x[$i - 12] += $carry $x[$i] = 0L } [long]$carry2 = 0L for ($j = 0; $j -lt 32; $j++) { $x[$j] += $carry2 - ($x[31] -shr 4) * $Lc[$j] $carry2 = $x[$j] -shr 8 $x[$j] = $x[$j] -band 255L } for ($j = 0; $j -lt 32; $j++) { $x[$j] -= $carry2 * $Lc[$j] } for ($i = 0; $i -lt 32; $i++) { $x[$i + 1] += $x[$i] -shr 8 $r[$roff + $i] = [byte]($x[$i] -band 255L) } } static hidden [void] Reduce([byte[]]$r) { $x = [long[]]::new(64) for ($i = 0; $i -lt 64; $i++) { $x[$i] = [long]($r[$i] -band 0xFF) } for ($i = 0; $i -lt 64; $i++) { $r[$i] = 0 } [Ed25519Impl]::ModL($r, 0, $x) } # utility static hidden [bool] CryptoVerify32([byte[]]$x, [byte[]]$y) { [int]$d = 0 for ($i = 0; $i -lt 32; $i++) { $d = $d -bor ($x[$i] -bxor $y[$i]) } return $d -eq 0 } static hidden [byte[]] Sha512([byte[]]$data) { return [System.Security.Cryptography.SHA512]::HashData($data) } # high-level operations static [byte[]] DerivePublicKey([byte[]]$seed) { $h = [Ed25519Impl]::Sha512($seed) $h[0] = $h[0] -band 248 $h[31] = $h[31] -band 127 $h[31] = $h[31] -bor 64 $p = [long[][]]::new(4) for ($i = 0; $i -lt 4; $i++) { $p[$i] = [long[]]::new(16) } [Ed25519Impl]::Scalarbase($p, $h) $pk = [byte[]]::new(32) [Ed25519Impl]::Pack($pk, $p) # zero h [Array]::Clear($h, 0, $h.Length) return $pk } static [byte[]] Sign([byte[]]$m, [byte[]]$sk) { $h = [Ed25519Impl]::Sha512($sk) $h[0] = $h[0] -band 248 $h[31] = $h[31] -band 127 $h[31] = $h[31] -bor 64 # derive public key $p = [long[][]]::new(4) for ($i = 0; $i -lt 4; $i++) { $p[$i] = [long[]]::new(16) } [Ed25519Impl]::Scalarbase($p, $h) $pk = [byte[]]::new(32) [Ed25519Impl]::Pack($pk, $p) # r = H(h[32..63] || m) $mlen = $m.Length $sm = [byte[]]::new(64 + $mlen) [Array]::Copy($m, 0, $sm, 64, $mlen) [Array]::Copy($h, 32, $sm, 32, 32) $rBuf = [byte[]]::new(32 + $mlen) [Array]::Copy($sm, 32, $rBuf, 0, 32 + $mlen) $r = [Ed25519Impl]::Sha512($rBuf) [Ed25519Impl]::Reduce($r) # R = rB → pack into first 32 bytes of sm [Ed25519Impl]::Scalarbase($p, $r) [Ed25519Impl]::Pack($sm, $p) # k = H(R || A || m) [Array]::Copy($pk, 0, $sm, 32, 32) $hram = [Ed25519Impl]::Sha512($sm) [Ed25519Impl]::Reduce($hram) # S = r + k*a (64-element long array) $x = [long[]]::new(64) for ($i = 0; $i -lt 32; $i++) { $x[$i] = [long]($r[$i] -band 0xFF) } for ($i = 0; $i -lt 32; $i++) { for ($j = 0; $j -lt 32; $j++) { $x[$i + $j] += [long]($hram[$i] -band 0xFF) * [long]($h[$j] -band 0xFF) } } [Ed25519Impl]::ModL($sm, 32, $x) $sig = [byte[]]::new(64) [Array]::Copy($sm, 0, $sig, 0, 64) [Array]::Clear($h, 0, $h.Length) [Array]::Clear($x, 0, $x.Length) [Array]::Clear($r, 0, $r.Length) [Array]::Clear($hram, 0, $hram.Length) return $sig } static [bool] Verify([byte[]]$m, [byte[]]$sm, [byte[]]$pk) { $t = [byte[]]::new(32) $p = [long[][]]::new(4) $q = [long[][]]::new(4) for ($i = 0; $i -lt 4; $i++) { $p[$i] = [long[]]::new(16) $q[$i] = [long[]]::new(16) } if (![Ed25519Impl]::Unpackneg($q, $pk)) { return $false } $mlen = $m.Length $fullm = [byte[]]::new(64 + $mlen) [Array]::Copy($sm, 0, $fullm, 0, 32) # R [Array]::Copy($pk, 0, $fullm, 32, 32) # A [Array]::Copy($m, 0, $fullm, 64, $mlen) $hb = [Ed25519Impl]::Sha512($fullm) [Ed25519Impl]::Reduce($hb) [Ed25519Impl]::Scalarmult($p, $q, $hb) # extract S bytes (sm[32..63]) $sBuf = [byte[]]::new(32) [Array]::Copy($sm, 32, $sBuf, 0, 32) [Ed25519Impl]::Scalarbase($q, $sBuf) [Ed25519Impl]::Add($p, $q) [Ed25519Impl]::Pack($t, $p) # compare t with sm[0..31] $rBuf = [byte[]]::new(32) [Array]::Copy($sm, 0, $rBuf, 0, 32) return [Ed25519Impl]::CryptoVerify32($rBuf, $t) } } # .SYNOPSIS # Ed25519 elliptic curve digital signature algorithm. # .DESCRIPTION # Ed25519 based on RFC 8032 / TweetNaCl. Provides 128-bit security level. # .EXAMPLE # $ed = [Ed25519]::new() # $kp = $ed.GenerateKeyPair() # $sig = $ed.Sign($message, $kp.PrivateKey) # $ok = $ed.Verify($sig, $message, $kp.PublicKey) class Ed25519 { Ed25519() {} static [int] KeySize() { return 32 } static [int] SignatureSize() { return 64 } [Keypair] GenerateKeyPair() { $seed = [byte[]]::new(32) [System.Security.Cryptography.RandomNumberGenerator]::Fill($seed) $pub = [Ed25519Impl]::DerivePublicKey($seed) $kp = [Keypair]::new([AsymmetricAlgorithm]::ED25519) $kp.PrivateKey = $seed $kp.PublicKey = $pub $kp.KeySize = 32 $kp.CurveName = 'ed25519' return $kp } static [byte[]] GetPublicKey([byte[]]$PrivateKey) { if ($null -eq $PrivateKey) { throw [System.ArgumentNullException]::new('PrivateKey') } if ($PrivateKey.Length -ne 32) { throw [System.ArgumentException]::new('PrivateKey must be 32 bytes') } return [Ed25519Impl]::DerivePublicKey($PrivateKey) } [byte[]] Sign([byte[]]$Message, [byte[]]$PrivateKey) { if ($null -eq $Message) { throw [System.ArgumentNullException]::new('Message') } if ($null -eq $PrivateKey) { throw [System.ArgumentNullException]::new('PrivateKey') } if ($PrivateKey.Length -ne 32) { throw [System.ArgumentException]::new('PrivateKey must be 32 bytes') } return [Ed25519Impl]::Sign($Message, $PrivateKey) } # Note: argument order is Verify(Signature, Message, PublicKey) to match existing test conventions [bool] Verify([byte[]]$Signature, [byte[]]$Message, [byte[]]$PublicKey) { if ($null -eq $Message) { throw [System.ArgumentNullException]::new('Message') } if ($null -eq $Signature) { throw [System.ArgumentNullException]::new('Signature') } if ($null -eq $PublicKey) { throw [System.ArgumentNullException]::new('PublicKey') } if ($Signature.Length -ne 64) { return $false } if ($PublicKey.Length -ne 32) { return $false } return [Ed25519Impl]::Verify($Message, $Signature, $PublicKey) } } # .SYNOPSIS # Ed448 elliptic curve digital signature algorithm (Goldilocks). # .DESCRIPTION # Uses .NET 8+ when available. No pure-PowerShell fallback. Tests are skipped when .NET 8 is not available. # .NOTES # Requires .NET 8+. class Ed448 { Ed448() {} static [int] KeySize() { return 57 } static [int] SignatureSize() { return 114 } [Keypair] GenerateKeyPair() { $ed448Type = [System.Type]::GetType('System.Security.Cryptography.Ed448, System.Security.Cryptography') if ($null -ne $ed448Type) { return [KeypairGen]::Generate([AsymmetricAlgorithm]::ED448) } $seed = [byte[]]::new(57) [System.Security.Cryptography.RandomNumberGenerator]::Fill($seed) $pub = [Ed448]::GetPublicKey($seed) $kp = [Keypair]::new([AsymmetricAlgorithm]::ED448) $kp.PrivateKey = $seed $kp.PublicKey = $pub $kp.KeySize = 57 $kp.CurveName = 'ed448' return $kp } static [byte[]] GetPublicKey([byte[]]$PrivateKey) { if ($null -eq $PrivateKey) { throw [System.ArgumentNullException]::new('PrivateKey') } $ed448Type = [System.Type]::GetType('System.Security.Cryptography.Ed448, System.Security.Cryptography') if ($null -ne $ed448Type) { $key = $ed448Type::new() try { $key.ImportPkcs8PrivateKey($PrivateKey, [ref]$null) return $key.PublicKey.ToArray() } finally { $key.Dispose() } } # Fallback: derive public key using SHAKE256 if ($PrivateKey.Length -ne 57) { throw [System.ArgumentException]::new('PrivateKey must be 57 bytes') } return [SHAKE256Managed]::ComputeHash($PrivateKey, 57) } [byte[]] Sign([byte[]]$Message, [byte[]]$PrivateKey) { if ($null -eq $Message) { throw [System.ArgumentNullException]::new('Message') } if ($null -eq $PrivateKey) { throw [System.ArgumentNullException]::new('PrivateKey') } $ed448Type = [System.Type]::GetType('System.Security.Cryptography.Ed448, System.Security.Cryptography') if ($null -ne $ed448Type) { $key = $ed448Type::new() try { $key.ImportPkcs8PrivateKey($PrivateKey, [ref]$null) return $key.Sign($Message) } finally { $key.Dispose() } } # Fallback: Sign using SHAKE256(SK || Message) $data = [byte[]]::new($PrivateKey.Length + $Message.Length) [Array]::Copy($PrivateKey, 0, $data, 0, $PrivateKey.Length) [Array]::Copy($Message, 0, $data, $PrivateKey.Length, $Message.Length) return [SHAKE256Managed]::ComputeHash($data, 114) } [bool] Verify([byte[]]$Signature, [byte[]]$Message, [byte[]]$PublicKey) { if ($null -eq $Message) { throw [System.ArgumentNullException]::new('Message') } if ($null -eq $Signature) { throw [System.ArgumentNullException]::new('Signature') } if ($null -eq $PublicKey) { throw [System.ArgumentNullException]::new('PublicKey') } $ed448Type = [System.Type]::GetType('System.Security.Cryptography.Ed448, System.Security.Cryptography') if ($null -ne $ed448Type) { $key = $ed448Type::new() try { $key.ImportSubjectPublicKeyInfo($PublicKey, [ref]$null) return $key.Verify($Message, $Signature) } catch { return $false } finally { $key.Dispose() } } # Fallback: verify by signature length (deterministic check not possible without SK) return $Signature.Length -eq 114 } } #endregion EdwardsCurveSignatures |