2025-05-07 10:05:13 +08:00
// Copyright 2025 Sun Yimin. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2025-05-09 10:24:21 +08:00
// Code generated by generate.go. DO NOT EDIT.
2025-05-07 10:05:13 +08:00
//go:build go1.24
package mldsa
import (
"crypto"
"crypto/sha3"
"crypto/subtle"
2025-05-07 15:08:36 +08:00
"encoding/asn1"
2025-05-07 10:05:13 +08:00
"errors"
"io"
)
// A PrivateKey65 is the private key for the ML-DSA-65 signature scheme.
type PrivateKey65 struct {
rho [ 32 ] byte // public random seed
k [ 32 ] byte // private random seed for signing
tr [ 64 ] byte // pre-cached public key Hash, H(pk, 64)
s1 [ l65 ] ringElement // private secret of size L with short coefficients (-4..4) or (-2..2)
s2 [ k65 ] ringElement // private secret of size K with short coefficients (-4..4) or (-2..2)
t0 [ k65 ] ringElement // the Polynomial encoding of the 13 LSB of each coefficient of the uncompressed public key polynomial t. This is saved as part of the private key.
a [ k65 * l65 ] nttElement // a is generated and stored in NTT representation
}
// A Key65 is the key pair for the ML-DSA-65 signature scheme.
type Key65 struct {
PrivateKey65
xi [ 32 ] byte // input seed
t1 [ k65 ] ringElement // the Polynomial encoding of the 10 MSB of each coefficient of the uncompressed public key polynomial t. This is saved as part of the public key.
}
// A PublicKey65 is the public key for the ML-DSA-65 signature scheme.
type PublicKey65 struct {
rho [ 32 ] byte
t1 [ k65 ] ringElement
tr [ 64 ] byte // H(pk, 64), need to further check if public key requires it
a [ k65 * l65 ] nttElement // a is generated and stored in NTT representation
}
// PublicKey generates and returns the corresponding public key for the given
// Key65 instance.
func ( sk * Key65 ) PublicKey ( ) * PublicKey65 {
return & PublicKey65 {
rho : sk . rho ,
t1 : sk . t1 ,
tr : sk . tr ,
a : sk . a ,
}
}
func ( pk * PublicKey65 ) Equal ( x crypto . PublicKey ) bool {
xx , ok := x . ( * PublicKey65 )
if ! ok {
return false
}
return pk . rho == xx . rho && pk . t1 == xx . t1
}
// Bytes converts the PublicKey65 instance into a byte slice.
// See FIPS 204, Algorithm 22, pkEncode()
func ( pk * PublicKey65 ) Bytes ( ) [ ] byte {
// The actual logic is in a separate function to outline this allocation.
b := make ( [ ] byte , 0 , PublicKeySize65 )
return pk . bytes ( b )
}
func ( pk * PublicKey65 ) bytes ( b [ ] byte ) [ ] byte {
b = append ( b , pk . rho [ : ] ... )
for _ , f := range pk . t1 {
b = simpleBitPack10Bits ( b , f )
}
return b
}
// Bytes returns the byte representation of the PrivateKey65.
// It copies the internal seed (xi) into a fixed-size byte array
// and returns it as a slice.
func ( sk * Key65 ) Bytes ( ) [ ] byte {
var b [ SeedSize ] byte
copy ( b [ : ] , sk . xi [ : ] )
return b [ : ]
}
// Bytes converts the PrivateKey65 instance into a byte slice.
// See FIPS 204, Algorithm 24, skEncode()
func ( sk * PrivateKey65 ) Bytes ( ) [ ] byte {
b := make ( [ ] byte , 0 , PrivateKeySize65 )
return sk . bytes ( b )
}
func ( sk * PrivateKey65 ) bytes ( b [ ] byte ) [ ] byte {
b = append ( b , sk . rho [ : ] ... )
b = append ( b , sk . k [ : ] ... )
b = append ( b , sk . tr [ : ] ... )
for _ , f := range sk . s1 {
b = bitPackSigned4 ( b , f )
}
for _ , f := range sk . s2 {
b = bitPackSigned4 ( b , f )
}
for _ , f := range sk . t0 {
b = bitPackSigned4096 ( b , f )
}
return b
}
func ( sk * PrivateKey65 ) Equal ( x any ) bool {
xx , ok := x . ( * PrivateKey65 )
if ! ok {
return false
}
return sk . rho == xx . rho && sk . k == xx . k && sk . tr == xx . tr &&
sk . s1 == xx . s1 && sk . s2 == xx . s2 && sk . t0 == xx . t0
}
// GenerateKey65 generates a new Key65 (ML-DSA-65) using the provided random source.
func GenerateKey65 ( rand io . Reader ) ( * Key65 , error ) {
// The actual logic is in a separate function to outline this allocation.
sk := & Key65 { }
return generateKey65 ( sk , rand )
}
func generateKey65 ( sk * Key65 , rand io . Reader ) ( * Key65 , error ) {
// Generate a random seed.
var seed [ SeedSize ] byte
if _ , err := io . ReadFull ( rand , seed [ : ] ) ; err != nil {
return nil , err
}
dsaKeyGen65 ( sk , & seed )
return sk , nil
}
// NewKey65 creates a new instance of Key65 using the provided seed.
func NewKey65 ( seed [ ] byte ) ( * Key65 , error ) {
// The actual logic is in a separate function to outline this allocation.
sk := & Key65 { }
return newPrivateKey65FromSeed ( sk , seed )
}
func newPrivateKey65FromSeed ( sk * Key65 , seed [ ] byte ) ( * Key65 , error ) {
if len ( seed ) != SeedSize {
return nil , errors . New ( "mldsa: invalid seed length" )
}
xi := ( * [ 32 ] byte ) ( seed )
dsaKeyGen65 ( sk , xi )
return sk , nil
}
func dsaKeyGen65 ( sk * Key65 , xi * [ 32 ] byte ) {
sk . xi = * xi
H := sha3 . NewSHAKE256 ( )
H . Write ( xi [ : ] )
H . Write ( [ ] byte { k65 } )
H . Write ( [ ] byte { l65 } )
K := make ( [ ] byte , 128 )
H . Read ( K )
rho , rho1 := K [ : 32 ] , K [ 32 : 96 ]
K = K [ 96 : ]
sk . rho = [ 32 ] byte ( rho )
sk . k = [ 32 ] byte ( K )
s1 := & sk . s1
s2 := & sk . s2
// Algorithm 33, ExpandS
for s := byte ( 0 ) ; s < l65 ; s ++ {
s1 [ s ] = rejBoundedPoly ( rho1 , eta4 , 0 , s )
}
for r := byte ( 0 ) ; r < k65 ; r ++ {
s2 [ r ] = rejBoundedPoly ( rho1 , eta4 , 0 , r + l65 )
}
// Using rho generate A' = A in NTT form
A := & sk . a
// Algorithm 32, ExpandA
for r := byte ( 0 ) ; r < k65 ; r ++ {
for s := byte ( 0 ) ; s < l65 ; s ++ {
A [ r * l65 + s ] = rejNTTPoly ( rho , s , r )
}
}
// t = NTT_inv(A' * NTT(s1)) + s2
var s1NTT [ l65 ] nttElement
var nttT [ k65 ] nttElement
for i := range s1 {
s1NTT [ i ] = ntt ( s1 [ i ] )
}
for i := range nttT {
for j := range s1NTT {
nttT [ i ] = polyAdd ( nttT [ i ] , nttMul ( s1NTT [ j ] , A [ i * l65 + j ] ) )
}
}
var t [ k65 ] ringElement
t0 := & sk . t0
t1 := & sk . t1
for i := range nttT {
t [ i ] = polyAdd ( inverseNTT ( nttT [ i ] ) , s2 [ i ] )
// compress t
for j := range n {
t1 [ i ] [ j ] , t0 [ i ] [ j ] = power2Round ( t [ i ] [ j ] )
}
}
H . Reset ( )
ek := sk . PublicKey ( ) . Bytes ( )
H . Write ( ek )
H . Read ( sk . tr [ : ] )
}
// NewPublicKey65 decode an public key from its encoded form.
// See FIPS 204, Algorithm 23 pkDecode()
func NewPublicKey65 ( b [ ] byte ) ( * PublicKey65 , error ) {
// The actual logic is in a separate function to outline this allocation.
pk := & PublicKey65 { }
return parsePublicKey65 ( pk , b )
}
// See FIPS 204, Algorithm 23 pkDecode()
func parsePublicKey65 ( pk * PublicKey65 , b [ ] byte ) ( * PublicKey65 , error ) {
if len ( b ) != PublicKeySize65 {
return nil , errors . New ( "mldsa: invalid public key length" )
}
H := sha3 . NewSHAKE256 ( )
H . Write ( b )
H . Read ( pk . tr [ : ] )
copy ( pk . rho [ : ] , b [ : 32 ] )
b = b [ 32 : ]
for i := range k65 {
simpleBitUnpack10Bits ( b , & pk . t1 [ i ] )
b = b [ encodingSize10 : ]
}
A := & pk . a
rho := pk . rho [ : ]
// Algorithm 32, ExpandA
for r := byte ( 0 ) ; r < k65 ; r ++ {
for s := byte ( 0 ) ; s < l65 ; s ++ {
A [ r * l65 + s ] = rejNTTPoly ( rho , s , r )
}
}
return pk , nil
}
// NewPrivateKey65 decode an private key from its encoded form.
// See FIPS 204, Algorithm 25 skDecode()
func NewPrivateKey65 ( b [ ] byte ) ( * PrivateKey65 , error ) {
// The actual logic is in a separate function to outline this allocation.
sk := & PrivateKey65 { }
return parsePrivateKey65 ( sk , b )
}
// See FIPS 204, Algorithm 25 skDecode()
// Decode a private key from its encoded form.
func parsePrivateKey65 ( sk * PrivateKey65 , b [ ] byte ) ( * PrivateKey65 , error ) {
if len ( b ) != PrivateKeySize65 {
return nil , errors . New ( "mldsa: invalid private key length" )
}
copy ( sk . rho [ : ] , b [ : 32 ] )
copy ( sk . k [ : ] , b [ 32 : 64 ] )
copy ( sk . tr [ : ] , b [ 64 : 128 ] )
b = b [ 128 : ]
for i := range l65 {
f , err := bitUnpackSigned4 ( b )
if err != nil {
return nil , err
}
sk . s1 [ i ] = f
b = b [ encodingSize4 : ]
}
for i := range k65 {
f , err := bitUnpackSigned4 ( b )
if err != nil {
return nil , err
}
sk . s2 [ i ] = f
b = b [ encodingSize4 : ]
}
for i := range k65 {
bitUnpackSigned4096 ( b , & sk . t0 [ i ] )
b = b [ encodingSize13 : ]
}
A := & sk . a
rho := sk . rho [ : ]
// Algorithm 32, ExpandA
for r := byte ( 0 ) ; r < k65 ; r ++ {
for s := byte ( 0 ) ; s < l65 ; s ++ {
A [ r * l65 + s ] = rejNTTPoly ( rho , s , r )
}
}
return sk , nil
}
2025-05-08 10:22:30 +08:00
// Sign generates a digital signature for the given message and context using the private key.
// It uses a random seed generated from the provided random source.
//
// Parameters:
// - rand: An io.Reader used to generate a random seed for signing.
// - message: The message to be signed. Must not be empty.
// - context: An optional context for domain separation. Must not exceed 255 bytes.
//
// Returns:
// - A byte slice containing the generated signature.
// - An error if the message is empty, the context is too long, or if there is an issue
// reading from the random source.
//
// Note:
// - The function uses SHAKE256 from the SHA-3 family for hashing.
// - The signing process involves generating a unique seed and a hash-based
// message digest (mu) before delegating to the internal signing function.
2025-05-07 10:05:13 +08:00
func ( sk * PrivateKey65 ) Sign ( rand io . Reader , message , context [ ] byte ) ( [ ] byte , error ) {
if len ( message ) == 0 {
return nil , errors . New ( "mldsa: empty message" )
}
if len ( context ) > 255 {
return nil , errors . New ( "mldsa: context too long" )
}
var seed [ SeedSize ] byte
if _ , err := io . ReadFull ( rand , seed [ : ] ) ; err != nil {
return nil , err
}
H := sha3 . NewSHAKE256 ( )
H . Write ( sk . tr [ : ] )
H . Write ( [ ] byte { 0 , byte ( len ( context ) ) } )
if len ( context ) > 0 {
H . Write ( context )
}
H . Write ( message )
var mu [ 64 ] byte
H . Read ( mu [ : ] )
return sk . signInternal ( seed [ : ] , mu [ : ] )
}
2025-05-08 10:22:30 +08:00
// SignWithPreHash generates a digital signature for the given message
// using the private key and additional context. It uses a given hashing algorithm
// from the OID to pre-hash the message before signing.
// It is similar to Sign but allows for pre-hashing the message.
func ( sk * PrivateKey65 ) SignWithPreHash ( rand io . Reader , message , context [ ] byte , oid asn1 . ObjectIdentifier ) ( [ ] byte , error ) {
2025-05-07 15:08:36 +08:00
if len ( message ) == 0 {
return nil , errors . New ( "mldsa: empty message" )
}
if len ( context ) > 255 {
return nil , errors . New ( "mldsa: context too long" )
}
preHashValue , err := preHash ( oid , message )
if err != nil {
return nil , err
}
var seed [ SeedSize ] byte
if _ , err := io . ReadFull ( rand , seed [ : ] ) ; err != nil {
return nil , err
}
H := sha3 . NewSHAKE256 ( )
H . Write ( sk . tr [ : ] )
H . Write ( [ ] byte { 1 , byte ( len ( context ) ) } )
if len ( context ) > 0 {
H . Write ( context )
}
H . Write ( preHashValue )
var mu [ 64 ] byte
H . Read ( mu [ : ] )
return sk . signInternal ( seed [ : ] , mu [ : ] )
}
2025-05-08 10:22:30 +08:00
// See FIPS 204, Algorithm 7 ML-DSA.Sign_internal()
2025-05-07 10:05:13 +08:00
func ( sk * PrivateKey65 ) signInternal ( seed , mu [ ] byte ) ( [ ] byte , error ) {
var s1NTT [ l65 ] nttElement
var s2NTT [ k65 ] nttElement
var t0NTT [ k65 ] nttElement
for i := range s1NTT {
s1NTT [ i ] = ntt ( sk . s1 [ i ] )
}
for i := range s2NTT {
s2NTT [ i ] = ntt ( sk . s2 [ i ] )
}
for i := range t0NTT {
t0NTT [ i ] = ntt ( sk . t0 [ i ] )
}
var rho2 [ 64 + 2 ] byte
H := sha3 . NewSHAKE256 ( )
H . Write ( sk . k [ : ] )
H . Write ( seed [ : ] )
H . Write ( mu [ : ] )
H . Read ( rho2 [ : 64 ] )
A := & sk . a
// rejection sampling loop
for kappa := 0 ; ; kappa = kappa + l65 {
// expand mask
var y [ l65 ] ringElement
for i := range l65 {
index := kappa + i
rho2 [ 64 ] = byte ( index )
rho2 [ 65 ] = byte ( index >> 8 )
y [ i ] = expandMask ( rho2 [ : ] , gamma1TwoPower19 )
}
// compute w and w1
var w , w1 [ k65 ] ringElement
var wNTT [ k65 ] nttElement
for i := range w {
for j := range y {
wNTT [ i ] = polyAdd ( wNTT [ i ] , nttMul ( ntt ( y [ j ] ) , A [ i * l65 + j ] ) )
}
w [ i ] = inverseNTT ( wNTT [ i ] )
// high bits
for j := range w [ i ] {
w1 [ i ] [ j ] = fieldElement ( compressHighBits ( w [ i ] [ j ] , gamma2QMinus1Div32 ) )
}
}
// commitment hash
var cTilde [ lambda192 / 4 ] byte
var w1Encoded [ encodingSize4 ] byte
H . Reset ( )
H . Write ( mu [ : ] )
for i := range k65 {
simpleBitPack4Bits ( w1Encoded [ : 0 ] , w1 [ i ] )
H . Write ( w1Encoded [ : ] )
}
H . Read ( cTilde [ : ] )
// verifier's challenge
cNTT := ntt ( sampleInBall ( cTilde [ : ] , tau49 ) )
var cs1 [ l65 ] ringElement
var cs2 [ k65 ] ringElement
var z [ l65 ] ringElement
var r0 [ k65 ] [ n ] int32
// compute <<cs1>> and z = <<cs1>> + y
for i := range l65 {
cs1 [ i ] = inverseNTT ( nttMul ( cNTT , s1NTT [ i ] ) )
z [ i ] = polyAdd ( cs1 [ i ] , y [ i ] )
}
// compute <<cs2>> and r0 = LowBits(w - <<cs2>>)
for i := range k65 {
cs2 [ i ] = inverseNTT ( nttMul ( cNTT , s2NTT [ i ] ) )
for j := range cs2 [ i ] {
_ , r0 [ i ] [ j ] = decompose ( fieldSub ( w [ i ] [ j ] , cs2 [ i ] [ j ] ) , gamma2QMinus1Div32 )
}
}
zNorm := vectorInfinityNorm ( z [ : ] , 0 )
r0Norm := vectorInfinityNormSigned ( r0 [ : ] , 0 )
// if zNorm >= gamma1 - beta || r0Norm >= gamma2 - beta, then continue
if subtle . ConstantTimeLessOrEq ( int ( gamma1TwoPower19 - beta65 ) , zNorm ) | subtle . ConstantTimeLessOrEq ( int ( gamma2QMinus1Div32 - beta65 ) , r0Norm ) == 1 {
continue
}
// compute <<ct0>>
var ct0 [ k65 ] ringElement
for i := range k65 {
ct0 [ i ] = inverseNTT ( nttMul ( cNTT , t0NTT [ i ] ) )
}
// compute infinity norm of <<ct0>>
ct0Norm := vectorInfinityNorm ( ct0 [ : ] , 0 )
// make hint
var hints [ k65 ] ringElement
vectorMakeHint ( ct0 [ : ] , cs2 [ : ] , w [ : ] , hints [ : ] , gamma2QMinus1Div32 )
// if the number of 1 in the hint is greater than omega or the infinity norm of <<ct0>> >= gamma2, then continue
if ( subtle . ConstantTimeLessOrEq ( int ( omega55 + 1 ) , vectorCountOnes ( hints [ : ] ) ) | subtle . ConstantTimeLessOrEq ( gamma2QMinus1Div32 , ct0Norm ) ) == 1 {
continue
}
// signature encoding
sig := make ( [ ] byte , 0 , sigEncodedLen65 )
sig = append ( sig , cTilde [ : ] ... )
for i := range l65 {
sig = bitPackSignedTwoPower19 ( sig , z [ i ] )
}
return hintBitPack ( sig , hints [ : ] , omega55 ) , nil
}
}
2025-05-08 10:22:30 +08:00
// Verify checks the validity of a given signature for a message and context
// using the public key.
2025-05-07 10:05:13 +08:00
func ( pk * PublicKey65 ) Verify ( sig [ ] byte , message , context [ ] byte ) bool {
if len ( message ) == 0 {
return false
}
if len ( context ) > 255 {
return false
}
if len ( sig ) != sigEncodedLen65 {
return false
}
H := sha3 . NewSHAKE256 ( )
H . Write ( pk . tr [ : ] )
H . Write ( [ ] byte { 0 , byte ( len ( context ) ) } )
2025-05-09 10:24:21 +08:00
if len ( context ) > 0 {
H . Write ( context )
}
2025-05-07 10:05:13 +08:00
H . Write ( message )
var mu [ 64 ] byte
H . Read ( mu [ : ] )
return pk . verifyInternal ( sig , mu [ : ] )
}
2025-05-08 10:22:30 +08:00
// VerifyWithPreHash verifies a signature using a message and additional context.
// It uses a given hashing algorithm from the OID to pre-hash the message before verifying.
func ( pk * PublicKey65 ) VerifyWithPreHash ( sig [ ] byte , message , context [ ] byte , oid asn1 . ObjectIdentifier ) bool {
2025-05-07 15:08:36 +08:00
if len ( message ) == 0 {
return false
}
if len ( context ) > 255 {
return false
}
if len ( sig ) != sigEncodedLen65 {
return false
}
preHashValue , err := preHash ( oid , message )
if err != nil {
return false
}
H := sha3 . NewSHAKE256 ( )
H . Write ( pk . tr [ : ] )
H . Write ( [ ] byte { 1 , byte ( len ( context ) ) } )
if len ( context ) > 0 {
H . Write ( context )
}
H . Write ( preHashValue )
var mu [ 64 ] byte
H . Read ( mu [ : ] )
return pk . verifyInternal ( sig , mu [ : ] )
}
2025-05-08 10:22:30 +08:00
// See FIPS 204, Algorithm 8 ML-DSA.Verify_internal()
2025-05-07 10:05:13 +08:00
func ( pk * PublicKey65 ) verifyInternal ( sig , mu [ ] byte ) bool {
// Decode the signature
cTilde := sig [ : lambda192 / 4 ]
sig = sig [ lambda192 / 4 : ]
var z [ l65 ] ringElement
for i := range l65 {
bitUnpackSignedTwoPower19 ( sig , & z [ i ] )
sig = sig [ encodingSize20 : ]
}
zNorm := vectorInfinityNorm ( z [ : ] , 0 )
var hints [ k65 ] ringElement
if ! hintBitUnpack ( sig , hints [ : ] , omega55 ) {
return false
}
// verifier's challenge
cNTT := ntt ( sampleInBall ( cTilde [ : ] , tau49 ) )
// t = t1 * 2^d
// tNTT = NTT(t)*cNTT
var tNTT [ k65 ] nttElement
t := pk . t1
for i := range k65 {
for j := range t [ i ] {
t [ i ] [ j ] <<= d
}
tNTT [ i ] = nttMul ( ntt ( t [ i ] ) , cNTT )
}
var w1 , wApprox [ k65 ] ringElement
var zNTT [ k65 ] nttElement
for i := range k65 {
for j := 0 ; j < l65 ; j ++ {
zNTT [ i ] = polyAdd ( zNTT [ i ] , nttMul ( ntt ( z [ j ] ) , pk . a [ i * l65 + j ] ) )
}
zNTT [ i ] = polySub ( zNTT [ i ] , tNTT [ i ] )
wApprox [ i ] = inverseNTT ( zNTT [ i ] )
}
H := sha3 . NewSHAKE256 ( )
H . Write ( mu [ : ] )
var w1Encoded [ encodingSize4 ] byte
for i := range k65 {
for j := range wApprox [ i ] {
w1 [ i ] [ j ] = useHint ( hints [ i ] [ j ] , wApprox [ i ] [ j ] , gamma2QMinus1Div32 )
}
simpleBitPack4Bits ( w1Encoded [ : 0 ] , w1 [ i ] )
H . Write ( w1Encoded [ : ] )
}
var cTilde1 [ lambda192 / 4 ] byte
H . Read ( cTilde1 [ : ] )
return subtle . ConstantTimeLessOrEq ( int ( gamma1TwoPower19 - beta65 ) , zNorm ) == 0 &&
subtle . ConstantTimeCompare ( cTilde [ : ] , cTilde1 [ : ] ) == 1
}