mirror of
https://github.com/emmansun/gmsm.git
synced 2025-09-16 03:43:50 +08:00
![github-actions[bot]](/assets/img/avatar_default.png)
* build(deps): bump github/codeql-action from 3.29.11 to 3.30.0 (#361) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.11 to 3.30.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](3c3833e0f8...2d92b76c45
) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.30.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump codecov/codecov-action from 5.5.0 to 5.5.1 (#362) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.5.0 to 5.5.1. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](fdcc847654...5a1091511a
) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 5.5.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump actions/setup-go from 5.5.0 to 6.0.0 (#363) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5.5.0 to 6.0.0. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](d35c59abb0...4469467582
) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump github/codeql-action from 3.30.0 to 3.30.1 (#364) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.30.0 to 3.30.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](2d92b76c45...f1f6e5f6af
) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.30.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump step-security/harden-runner from 2.13.0 to 2.13.1 (#367) Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.13.0 to 2.13.1. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](ec9f2d5744...f4a75cfd61
) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.13.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump github/codeql-action from 3.30.1 to 3.30.2 (#368) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.30.1 to 3.30.2. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](f1f6e5f6af...d3678e237b
) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.30.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat(mlkem): initialize mlkem from golang standard library * chore(mlkem): refactoring, reduce alloc times * build(deps): bump github/codeql-action from 3.30.2 to 3.30.3 (#369) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.30.2 to 3.30.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](d3678e237b...192325c861
) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.30.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * doc(README): include MLKEM * mldsa: refactor the implementation of key and sign/verify * mldsa,slhdsa: crypto.Signer assertion * fix(slhdsa): GenerateKey slice issue #72 * fix(slhdsa): copy/paste issue --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sun Yimin <emmansun@users.noreply.github.com>
638 lines
16 KiB
Go
638 lines
16 KiB
Go
// 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.
|
|
// Code generated by generate.go. DO NOT EDIT.
|
|
|
|
//go:build go1.24
|
|
|
|
package mldsa
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/sha3"
|
|
"crypto/subtle"
|
|
"encoding/asn1"
|
|
"errors"
|
|
"io"
|
|
"sync"
|
|
)
|
|
|
|
var _ crypto.Signer = (*PrivateKey87)(nil)
|
|
var _ crypto.Signer = (*Key87)(nil)
|
|
|
|
// A PrivateKey87 is the private key for the ML-DSA-87 signature scheme.
|
|
type PrivateKey87 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 [l87]ringElement // private secret of size L with short coefficients (-4..4) or (-2..2)
|
|
s2 [k87]ringElement // private secret of size K with short coefficients (-4..4) or (-2..2)
|
|
t0 [k87]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.
|
|
t1 [k87]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.
|
|
s1NTTCache [l87]nttElement
|
|
s2NTTCache [k87]nttElement
|
|
t0NTTCache [k87]nttElement
|
|
a [k87 * l87]nttElement // a is generated and stored in NTT representation
|
|
nttOnce sync.Once
|
|
t1Once sync.Once
|
|
}
|
|
|
|
// Public returns the public key corresponding to the private key.
|
|
// Although we can derive the public key from the private key,
|
|
// but we do NOT need to derive it at most of the time.
|
|
func (sk *PrivateKey87) Public() crypto.PublicKey {
|
|
sk.ensureT1()
|
|
return &PublicKey87{
|
|
rho: sk.rho,
|
|
t1: sk.t1,
|
|
tr: sk.tr,
|
|
a: sk.a,
|
|
}
|
|
}
|
|
|
|
func (sk *PrivateKey87) ensureNTT() {
|
|
sk.nttOnce.Do(func() {
|
|
for i := range sk.s1NTTCache {
|
|
sk.s1NTTCache[i] = ntt(sk.s1[i])
|
|
}
|
|
for i := range sk.s2NTTCache {
|
|
sk.s2NTTCache[i] = ntt(sk.s2[i])
|
|
}
|
|
for i := range sk.t0NTTCache {
|
|
sk.t0NTTCache[i] = ntt(sk.t0[i])
|
|
}
|
|
})
|
|
}
|
|
|
|
func (sk *PrivateKey87) ensureT1() {
|
|
sk.ensureNTT()
|
|
sk.t1Once.Do(func() {
|
|
// t = NTT_inv(A' * NTT(s1)) + s2
|
|
s1NTT := sk.s1NTTCache
|
|
A := sk.a
|
|
s2 := sk.s2
|
|
var nttT [k87]nttElement
|
|
|
|
for i := range nttT {
|
|
for j := range s1NTT {
|
|
nttT[i] = polyAdd(nttT[i], nttMul(s1NTT[j], A[i*l87+j]))
|
|
}
|
|
}
|
|
var t [k87]ringElement
|
|
t1 := &sk.t1
|
|
for i := range nttT {
|
|
t[i] = polyAdd(inverseNTT(nttT[i]), s2[i])
|
|
// compress t
|
|
for j := range n {
|
|
t1[i][j], _ = power2Round(t[i][j])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// A Key87 is the key pair for the ML-DSA-87 signature scheme.
|
|
type Key87 struct {
|
|
PrivateKey87
|
|
xi [32]byte // input seed
|
|
}
|
|
|
|
// A PublicKey87 is the public key for the ML-DSA-87 signature scheme.
|
|
type PublicKey87 struct {
|
|
rho [32]byte
|
|
t1 [k87]ringElement
|
|
tr [64]byte // H(pk, 64), need to further check if public key requires it
|
|
tNTTCache [k87]nttElement
|
|
a [k87 * l87]nttElement // a is generated and stored in NTT representation
|
|
nttOnce sync.Once
|
|
}
|
|
|
|
// Public generates and returns the corresponding public key for the given
|
|
// Key87 instance.
|
|
func (sk *Key87) Public() crypto.PublicKey {
|
|
return &PublicKey87{
|
|
rho: sk.rho,
|
|
t1: sk.t1,
|
|
tr: sk.tr,
|
|
a: sk.a,
|
|
}
|
|
}
|
|
|
|
// Seed returns a byte slice of the secret key's seed value.
|
|
func (sk *Key87) Seed() []byte {
|
|
var b [SeedSize]byte
|
|
copy(b[:], sk.xi[:])
|
|
return b[:]
|
|
}
|
|
|
|
func (pk *PublicKey87) Equal(x crypto.PublicKey) bool {
|
|
xx, ok := x.(*PublicKey87)
|
|
if !ok {
|
|
return false
|
|
}
|
|
eq := subtle.ConstantTimeCompare(pk.rho[:], xx.rho[:]) &
|
|
constantTimeEqualRingElementArray(pk.t1[:], xx.t1[:])
|
|
return eq == 1
|
|
}
|
|
|
|
// Bytes converts the PublicKey87 instance into a byte slice.
|
|
// See FIPS 204, Algorithm 22, pkEncode()
|
|
func (pk *PublicKey87) Bytes() []byte {
|
|
// The actual logic is in a separate function to outline this allocation.
|
|
b := make([]byte, 0, PublicKeySize87)
|
|
return pk.bytes(b)
|
|
}
|
|
|
|
func (pk *PublicKey87) bytes(b []byte) []byte {
|
|
b = append(b, pk.rho[:]...)
|
|
for _, f := range pk.t1 {
|
|
b = simpleBitPack10Bits(b, f)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (pk *PublicKey87) ensureNTT() {
|
|
pk.nttOnce.Do(func() {
|
|
t := pk.t1
|
|
for i := range k87 {
|
|
for j := range t[i] {
|
|
t[i][j] <<= d
|
|
}
|
|
pk.tNTTCache[i] = ntt(t[i])
|
|
}
|
|
})
|
|
}
|
|
|
|
// Bytes converts the PrivateKey87 instance into a byte slice.
|
|
// See FIPS 204, Algorithm 24, skEncode()
|
|
func (sk *PrivateKey87) Bytes() []byte {
|
|
b := make([]byte, 0, PrivateKeySize87)
|
|
return sk.bytes(b)
|
|
}
|
|
|
|
func (sk *PrivateKey87) 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 = bitPackSigned2(b, f)
|
|
}
|
|
for _, f := range sk.s2 {
|
|
b = bitPackSigned2(b, f)
|
|
}
|
|
for _, f := range sk.t0 {
|
|
b = bitPackSigned4096(b, f)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (sk *PrivateKey87) Equal(x any) bool {
|
|
xx, ok := x.(*PrivateKey87)
|
|
if !ok {
|
|
return false
|
|
}
|
|
eq := subtle.ConstantTimeCompare(sk.rho[:], xx.rho[:]) &
|
|
subtle.ConstantTimeCompare(sk.k[:], xx.k[:]) &
|
|
subtle.ConstantTimeCompare(sk.tr[:], xx.tr[:]) &
|
|
constantTimeEqualRingElementArray(sk.s1[:], xx.s1[:]) &
|
|
constantTimeEqualRingElementArray(sk.s2[:], xx.s2[:]) &
|
|
constantTimeEqualRingElementArray(sk.t0[:], xx.t0[:])
|
|
return eq == 1
|
|
}
|
|
|
|
// GenerateKey87 generates a new Key87 (ML-DSA-87) using the provided random source.
|
|
func GenerateKey87(rand io.Reader) (*Key87, error) {
|
|
// The actual logic is in a separate function to outline this allocation.
|
|
sk := &Key87{}
|
|
return generateKey87(sk, rand)
|
|
}
|
|
|
|
func generateKey87(sk *Key87, rand io.Reader) (*Key87, error) {
|
|
// Generate a random seed.
|
|
var seed [SeedSize]byte
|
|
if _, err := io.ReadFull(rand, seed[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
dsaKeyGen87(sk, &seed)
|
|
return sk, nil
|
|
}
|
|
|
|
// NewKey87 creates a new instance of Key87 using the provided seed.
|
|
func NewKey87(seed []byte) (*Key87, error) {
|
|
// The actual logic is in a separate function to outline this allocation.
|
|
sk := &Key87{}
|
|
return newPrivateKey87FromSeed(sk, seed)
|
|
}
|
|
|
|
func newPrivateKey87FromSeed(sk *Key87, seed []byte) (*Key87, error) {
|
|
if len(seed) != SeedSize {
|
|
return nil, errors.New("mldsa: invalid seed length")
|
|
}
|
|
xi := (*[32]byte)(seed)
|
|
dsaKeyGen87(sk, xi)
|
|
return sk, nil
|
|
}
|
|
|
|
func dsaKeyGen87(sk *Key87, xi *[32]byte) {
|
|
sk.xi = *xi
|
|
H := sha3.NewSHAKE256()
|
|
H.Write(xi[:])
|
|
H.Write([]byte{k87, l87})
|
|
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 := range byte(l87) {
|
|
s1[s] = rejBoundedPoly(rho1, eta2, 0, s)
|
|
}
|
|
for r := range byte(k87) {
|
|
s2[r] = rejBoundedPoly(rho1, eta2, 0, r+l87)
|
|
}
|
|
|
|
// Using rho generate A' = A in NTT form
|
|
A := &sk.a
|
|
// Algorithm 32, ExpandA
|
|
for r := range byte(k87) {
|
|
for s := byte(0); s < l87; s++ {
|
|
A[r*l87+s] = rejNTTPoly(rho, s, r)
|
|
}
|
|
}
|
|
|
|
// t = NTT_inv(A' * NTT(s1)) + s2
|
|
var s1NTT [l87]nttElement
|
|
var nttT [k87]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*l87+j]))
|
|
}
|
|
}
|
|
var t [k87]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.Public().(*PublicKey87).Bytes()
|
|
H.Write(ek)
|
|
H.Read(sk.tr[:])
|
|
}
|
|
|
|
// NewPublicKey87 decode an public key from its encoded form.
|
|
// See FIPS 204, Algorithm 23 pkDecode()
|
|
func NewPublicKey87(b []byte) (*PublicKey87, error) {
|
|
// The actual logic is in a separate function to outline this allocation.
|
|
pk := &PublicKey87{}
|
|
return parsePublicKey87(pk, b)
|
|
}
|
|
|
|
// See FIPS 204, Algorithm 23 pkDecode()
|
|
func parsePublicKey87(pk *PublicKey87, b []byte) (*PublicKey87, error) {
|
|
if len(b) != PublicKeySize87 {
|
|
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 k87 {
|
|
simpleBitUnpack10Bits(b, &pk.t1[i])
|
|
b = b[encodingSize10:]
|
|
}
|
|
|
|
A := &pk.a
|
|
rho := pk.rho[:]
|
|
// Algorithm 32, ExpandA
|
|
for r := range byte(k87) {
|
|
for s := range byte(l87) {
|
|
A[r*l87+s] = rejNTTPoly(rho, s, r)
|
|
}
|
|
}
|
|
return pk, nil
|
|
}
|
|
|
|
// NewPrivateKey87 decode an private key from its encoded form.
|
|
// See FIPS 204, Algorithm 25 skDecode()
|
|
func NewPrivateKey87(b []byte) (*PrivateKey87, error) {
|
|
// The actual logic is in a separate function to outline this allocation.
|
|
sk := &PrivateKey87{}
|
|
return parsePrivateKey87(sk, b)
|
|
}
|
|
|
|
// See FIPS 204, Algorithm 25 skDecode()
|
|
// Decode a private key from its encoded form.
|
|
func parsePrivateKey87(sk *PrivateKey87, b []byte) (*PrivateKey87, error) {
|
|
if len(b) != PrivateKeySize87 {
|
|
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 l87 {
|
|
f, err := bitUnpackSigned2(b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sk.s1[i] = f
|
|
b = b[encodingSize3:]
|
|
}
|
|
for i := range k87 {
|
|
f, err := bitUnpackSigned2(b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sk.s2[i] = f
|
|
b = b[encodingSize3:]
|
|
}
|
|
for i := range k87 {
|
|
bitUnpackSigned4096(b, &sk.t0[i])
|
|
b = b[encodingSize13:]
|
|
}
|
|
A := &sk.a
|
|
rho := sk.rho[:]
|
|
// Algorithm 32, ExpandA
|
|
for r := range byte(k87) {
|
|
for s := range byte(l87) {
|
|
A[r*l87+s] = rejNTTPoly(rho, s, r)
|
|
}
|
|
}
|
|
return sk, nil
|
|
}
|
|
|
|
// Sign signs the provided digest using the private key. It is a wrapper around SignMessage.
|
|
// It satisfies the crypto.Signer interface.
|
|
func (sk *PrivateKey87) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
|
return sk.SignMessage(rand, digest, opts)
|
|
}
|
|
|
|
// SignMessage signs a message with the private key.
|
|
// It satisfies the crypto.MessageSigner interface.
|
|
//
|
|
// The function supports pre-hashing the message by providing a hash OID in the options.
|
|
// Context data can also be provided, but is limited to 255 bytes.
|
|
func (sk *PrivateKey87) SignMessage(rand io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error) {
|
|
var (
|
|
context []byte
|
|
hashOID asn1.ObjectIdentifier
|
|
indicator byte = 0
|
|
)
|
|
if opts, ok := opts.(*Options); ok {
|
|
context = opts.Context
|
|
hashOID = opts.PrehashOID
|
|
}
|
|
if len(hashOID) != 0 {
|
|
var err error
|
|
if message, err = preHash(hashOID, message); err != nil {
|
|
return nil, err
|
|
}
|
|
indicator = 1
|
|
}
|
|
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{indicator, 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[:])
|
|
}
|
|
|
|
// See FIPS 204, Algorithm 7 ML-DSA.Sign_internal()
|
|
func (sk *PrivateKey87) signInternal(seed, mu []byte) ([]byte, error) {
|
|
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
|
|
|
|
sk.ensureNTT()
|
|
zNormThreshold := int(gamma1TwoPower19 - beta87)
|
|
r0NormThreshold := int(gamma2QMinus1Div32 - beta87)
|
|
|
|
// rejection sampling loop
|
|
for kappa := 0; ; kappa += l87 {
|
|
// expand mask
|
|
var (
|
|
y [l87]ringElement
|
|
yNTT [l87]nttElement
|
|
)
|
|
for i := range l87 {
|
|
index := kappa + i
|
|
rho2[64] = byte(index)
|
|
rho2[65] = byte(index >> 8)
|
|
y[i] = expandMask(rho2[:], gamma1TwoPower19)
|
|
}
|
|
// compute y in NTT form
|
|
for i := range l87 {
|
|
yNTT[i] = ntt(y[i])
|
|
}
|
|
// compute w and w1
|
|
var (
|
|
w, w1 [k87]ringElement
|
|
wNTT [k87]nttElement
|
|
)
|
|
for i := range w {
|
|
for j := range yNTT {
|
|
wNTT[i] = polyAdd(wNTT[i], nttMul(yNTT[j], A[i*l87+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 [lambda256 / 4]byte
|
|
w1Encoded [encodingSize4]byte
|
|
)
|
|
H.Reset()
|
|
H.Write(mu[:])
|
|
for i := range k87 {
|
|
simpleBitPack4Bits(w1Encoded[:0], w1[i])
|
|
H.Write(w1Encoded[:])
|
|
}
|
|
H.Read(cTilde[:])
|
|
// verifier's challenge
|
|
cNTT := ntt(sampleInBall(cTilde[:], tau60))
|
|
|
|
var (
|
|
cs1 [l87]ringElement
|
|
cs2 [k87]ringElement
|
|
z [l87]ringElement
|
|
r0 [k87][n]int32
|
|
)
|
|
// compute <<cs1>> and z = <<cs1>> + y
|
|
for i := range l87 {
|
|
cs1[i] = inverseNTT(nttMul(cNTT, sk.s1NTTCache[i]))
|
|
z[i] = polyAdd(cs1[i], y[i])
|
|
}
|
|
// compute <<cs2>> and r0 = LowBits(w - <<cs2>>)
|
|
for i := range k87 {
|
|
cs2[i] = inverseNTT(nttMul(cNTT, sk.s2NTTCache[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(zNormThreshold, zNorm)|subtle.ConstantTimeLessOrEq(r0NormThreshold, r0Norm) == 1 {
|
|
continue
|
|
}
|
|
// compute <<ct0>>
|
|
var ct0 [k87]ringElement
|
|
for i := range k87 {
|
|
ct0[i] = inverseNTT(nttMul(cNTT, sk.t0NTTCache[i]))
|
|
}
|
|
// compute infinity norm of <<ct0>>
|
|
ct0Norm := vectorInfinityNorm(ct0[:], 0)
|
|
// make hint
|
|
var hints [k87]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(omega75+1), vectorCountOnes(hints[:])) | subtle.ConstantTimeLessOrEq(gamma2QMinus1Div32, ct0Norm)) == 1 {
|
|
continue
|
|
}
|
|
// signature encoding
|
|
sig := make([]byte, 0, sigEncodedLen87)
|
|
sig = append(sig, cTilde[:]...)
|
|
for i := range l87 {
|
|
sig = bitPackSignedTwoPower19(sig, z[i])
|
|
}
|
|
return hintBitPack(sig, hints[:], omega75), nil
|
|
}
|
|
}
|
|
|
|
// VerifyWithOptions verifies a signature against a message using the public key with additional options.
|
|
func (pk *PublicKey87) VerifyWithOptions(sig []byte, message []byte, opts crypto.SignerOpts) bool {
|
|
var (
|
|
context []byte
|
|
hashOID asn1.ObjectIdentifier
|
|
indicator byte = 0
|
|
)
|
|
if opts, ok := opts.(*Options); ok {
|
|
context = opts.Context
|
|
hashOID = opts.PrehashOID
|
|
}
|
|
if len(hashOID) != 0 {
|
|
var err error
|
|
if message, err = preHash(hashOID, message); err != nil {
|
|
return false
|
|
}
|
|
indicator = 1
|
|
}
|
|
|
|
if len(message) == 0 {
|
|
return false
|
|
}
|
|
if len(context) > 255 {
|
|
return false
|
|
}
|
|
if len(sig) != sigEncodedLen87 {
|
|
return false
|
|
}
|
|
H := sha3.NewSHAKE256()
|
|
H.Write(pk.tr[:])
|
|
H.Write([]byte{indicator, byte(len(context))})
|
|
if len(context) > 0 {
|
|
H.Write(context)
|
|
}
|
|
H.Write(message)
|
|
var mu [64]byte
|
|
H.Read(mu[:])
|
|
|
|
return pk.verifyInternal(sig, mu[:])
|
|
}
|
|
|
|
// See FIPS 204, Algorithm 8 ML-DSA.Verify_internal()
|
|
func (pk *PublicKey87) verifyInternal(sig, mu []byte) bool {
|
|
// Decode the signature
|
|
cTilde := sig[:lambda256/4]
|
|
sig = sig[lambda256/4:]
|
|
|
|
var (
|
|
z [l87]ringElement
|
|
zNTT [l87]nttElement
|
|
)
|
|
for i := range l87 {
|
|
bitUnpackSignedTwoPower19(sig, &z[i])
|
|
zNTT[i] = ntt(z[i])
|
|
sig = sig[encodingSize20:]
|
|
}
|
|
zNorm := vectorInfinityNorm(z[:], 0)
|
|
var hints [k87]ringElement
|
|
if !hintBitUnpack(sig, hints[:], omega75) {
|
|
return false
|
|
}
|
|
// verifier's challenge
|
|
cNTT := ntt(sampleInBall(cTilde[:], tau60))
|
|
|
|
pk.ensureNTT()
|
|
// tNTT = tNTTCache*cNTT
|
|
var tNTT [k87]nttElement
|
|
for i := range k87 {
|
|
tNTT[i] = nttMul(pk.tNTTCache[i], cNTT)
|
|
}
|
|
|
|
var (
|
|
w1, wApprox [k87]ringElement
|
|
zNTTMulA [k87]nttElement
|
|
)
|
|
for i := range k87 {
|
|
for j := range l87 {
|
|
zNTTMulA[i] = polyAdd(zNTTMulA[i], nttMul(zNTT[j], pk.a[i*l87+j]))
|
|
}
|
|
zNTTMulA[i] = polySub(zNTTMulA[i], tNTT[i])
|
|
wApprox[i] = inverseNTT(zNTTMulA[i])
|
|
}
|
|
|
|
H := sha3.NewSHAKE256()
|
|
H.Write(mu[:])
|
|
var w1Encoded [encodingSize4]byte
|
|
for i := range k87 {
|
|
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 [lambda256 / 4]byte
|
|
H.Read(cTilde1[:])
|
|
return subtle.ConstantTimeLessOrEq(int(gamma1TwoPower19-beta87), zNorm) == 0 &&
|
|
subtle.ConstantTimeCompare(cTilde, cTilde1[:]) == 1
|
|
}
|