2022-07-07 20:11:50 +00:00
|
|
|
|
// Copyright 2011 The Go Authors. All rights reserved.
|
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
|
|
package openpgp
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"crypto"
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
"crypto/rsa"
|
|
|
|
|
goerrors "errors"
|
|
|
|
|
"io"
|
|
|
|
|
"math/big"
|
2024-10-16 10:54:40 +00:00
|
|
|
|
"time"
|
2022-07-07 20:11:50 +00:00
|
|
|
|
|
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp/ecdh"
|
2024-10-16 10:54:40 +00:00
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp/ecdsa"
|
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp/eddsa"
|
2022-07-07 20:11:50 +00:00
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
2024-10-16 10:54:40 +00:00
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp/internal/ecc"
|
2022-07-07 20:11:50 +00:00
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// NewEntity returns an Entity that contains a fresh RSA/RSA keypair with a
|
|
|
|
|
// single identity composed of the given full name, comment and email, any of
|
|
|
|
|
// which may be empty but must not contain any of "()<>\x00".
|
|
|
|
|
// If config is nil, sensible defaults will be used.
|
|
|
|
|
func NewEntity(name, comment, email string, config *packet.Config) (*Entity, error) {
|
|
|
|
|
creationTime := config.Now()
|
|
|
|
|
keyLifetimeSecs := config.KeyLifetime()
|
|
|
|
|
|
|
|
|
|
// Generate a primary signing key
|
|
|
|
|
primaryPrivRaw, err := newSigner(config)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
primary := packet.NewSignerPrivateKey(creationTime, primaryPrivRaw)
|
|
|
|
|
if config != nil && config.V5Keys {
|
|
|
|
|
primary.UpgradeToV5()
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
|
e := &Entity{
|
|
|
|
|
PrimaryKey: &primary.PublicKey,
|
|
|
|
|
PrivateKey: primary,
|
|
|
|
|
Identities: make(map[string]*Identity),
|
|
|
|
|
Subkeys: []Subkey{},
|
2022-07-07 20:11:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
|
err = e.addUserId(name, comment, email, config, creationTime, keyLifetimeSecs)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NOTE: No key expiry here, but we will not return this subkey in EncryptionKey()
|
|
|
|
|
// if the primary/master key has expired.
|
|
|
|
|
err = e.addEncryptionSubkey(config, creationTime, 0)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return e, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Entity) AddUserId(name, comment, email string, config *packet.Config) error {
|
|
|
|
|
creationTime := config.Now()
|
|
|
|
|
keyLifetimeSecs := config.KeyLifetime()
|
|
|
|
|
return t.addUserId(name, comment, email, config, creationTime, keyLifetimeSecs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Entity) addUserId(name, comment, email string, config *packet.Config, creationTime time.Time, keyLifetimeSecs uint32) error {
|
|
|
|
|
uid := packet.NewUserId(name, comment, email)
|
|
|
|
|
if uid == nil {
|
|
|
|
|
return errors.InvalidArgumentError("user id field contained invalid characters")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, ok := t.Identities[uid.Id]; ok {
|
|
|
|
|
return errors.InvalidArgumentError("user id exist")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
primary := t.PrivateKey
|
|
|
|
|
|
|
|
|
|
isPrimaryId := len(t.Identities) == 0
|
|
|
|
|
|
|
|
|
|
selfSignature := createSignaturePacket(&primary.PublicKey, packet.SigTypePositiveCert, config)
|
|
|
|
|
selfSignature.CreationTime = creationTime
|
|
|
|
|
selfSignature.KeyLifetimeSecs = &keyLifetimeSecs
|
|
|
|
|
selfSignature.IsPrimaryId = &isPrimaryId
|
|
|
|
|
selfSignature.FlagsValid = true
|
|
|
|
|
selfSignature.FlagSign = true
|
|
|
|
|
selfSignature.FlagCertify = true
|
|
|
|
|
selfSignature.SEIPDv1 = true // true by default, see 5.8 vs. 5.14
|
|
|
|
|
selfSignature.SEIPDv2 = config.AEAD() != nil
|
|
|
|
|
|
2022-07-07 20:11:50 +00:00
|
|
|
|
// Set the PreferredHash for the SelfSignature from the packet.Config.
|
|
|
|
|
// If it is not the must-implement algorithm from rfc4880bis, append that.
|
2024-10-16 10:54:40 +00:00
|
|
|
|
hash, ok := algorithm.HashToHashId(config.Hash())
|
|
|
|
|
if !ok {
|
|
|
|
|
return errors.UnsupportedError("unsupported preferred hash function")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
selfSignature.PreferredHash = []uint8{hash}
|
2022-07-07 20:11:50 +00:00
|
|
|
|
if config.Hash() != crypto.SHA256 {
|
|
|
|
|
selfSignature.PreferredHash = append(selfSignature.PreferredHash, hashToHashId(crypto.SHA256))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Likewise for DefaultCipher.
|
|
|
|
|
selfSignature.PreferredSymmetric = []uint8{uint8(config.Cipher())}
|
|
|
|
|
if config.Cipher() != packet.CipherAES128 {
|
|
|
|
|
selfSignature.PreferredSymmetric = append(selfSignature.PreferredSymmetric, uint8(packet.CipherAES128))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We set CompressionNone as the preferred compression algorithm because
|
|
|
|
|
// of compression side channel attacks, then append the configured
|
|
|
|
|
// DefaultCompressionAlgo if any is set (to signal support for cases
|
|
|
|
|
// where the application knows that using compression is safe).
|
|
|
|
|
selfSignature.PreferredCompression = []uint8{uint8(packet.CompressionNone)}
|
|
|
|
|
if config.Compression() != packet.CompressionNone {
|
|
|
|
|
selfSignature.PreferredCompression = append(selfSignature.PreferredCompression, uint8(config.Compression()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// And for DefaultMode.
|
2024-10-16 10:54:40 +00:00
|
|
|
|
modes := []uint8{uint8(config.AEAD().Mode())}
|
|
|
|
|
if config.AEAD().Mode() != packet.AEADModeOCB {
|
|
|
|
|
modes = append(modes, uint8(packet.AEADModeOCB))
|
2022-07-07 20:11:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
|
// For preferred (AES256, GCM), we'll generate (AES256, GCM), (AES256, OCB), (AES128, GCM), (AES128, OCB)
|
|
|
|
|
for _, cipher := range selfSignature.PreferredSymmetric {
|
|
|
|
|
for _, mode := range modes {
|
|
|
|
|
selfSignature.PreferredCipherSuites = append(selfSignature.PreferredCipherSuites, [2]uint8{cipher, mode})
|
|
|
|
|
}
|
2022-07-07 20:11:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
|
// User ID binding signature
|
|
|
|
|
err := selfSignature.SignUserId(uid.Id, &primary.PublicKey, primary, config)
|
2022-07-07 20:11:50 +00:00
|
|
|
|
if err != nil {
|
2024-10-16 10:54:40 +00:00
|
|
|
|
return err
|
2022-07-07 20:11:50 +00:00
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
|
t.Identities[uid.Id] = &Identity{
|
|
|
|
|
Name: uid.Id,
|
|
|
|
|
UserId: uid,
|
|
|
|
|
SelfSignature: selfSignature,
|
|
|
|
|
Signatures: []*packet.Signature{selfSignature},
|
2022-07-07 20:11:50 +00:00
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
|
return nil
|
2022-07-07 20:11:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AddSigningSubkey adds a signing keypair as a subkey to the Entity.
|
|
|
|
|
// If config is nil, sensible defaults will be used.
|
|
|
|
|
func (e *Entity) AddSigningSubkey(config *packet.Config) error {
|
|
|
|
|
creationTime := config.Now()
|
|
|
|
|
keyLifetimeSecs := config.KeyLifetime()
|
|
|
|
|
|
|
|
|
|
subPrivRaw, err := newSigner(config)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
sub := packet.NewSignerPrivateKey(creationTime, subPrivRaw)
|
2024-10-16 10:54:40 +00:00
|
|
|
|
sub.IsSubkey = true
|
|
|
|
|
if config != nil && config.V5Keys {
|
|
|
|
|
sub.UpgradeToV5()
|
|
|
|
|
}
|
2022-07-07 20:11:50 +00:00
|
|
|
|
|
|
|
|
|
subkey := Subkey{
|
|
|
|
|
PublicKey: &sub.PublicKey,
|
|
|
|
|
PrivateKey: sub,
|
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
|
subkey.Sig = createSignaturePacket(e.PrimaryKey, packet.SigTypeSubkeyBinding, config)
|
|
|
|
|
subkey.Sig.CreationTime = creationTime
|
|
|
|
|
subkey.Sig.KeyLifetimeSecs = &keyLifetimeSecs
|
|
|
|
|
subkey.Sig.FlagsValid = true
|
|
|
|
|
subkey.Sig.FlagSign = true
|
|
|
|
|
subkey.Sig.EmbeddedSignature = createSignaturePacket(subkey.PublicKey, packet.SigTypePrimaryKeyBinding, config)
|
|
|
|
|
subkey.Sig.EmbeddedSignature.CreationTime = creationTime
|
2022-07-07 20:11:50 +00:00
|
|
|
|
|
|
|
|
|
err = subkey.Sig.EmbeddedSignature.CrossSignKey(subkey.PublicKey, e.PrimaryKey, subkey.PrivateKey, config)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
|
err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config)
|
|
|
|
|
if err != nil {
|
2022-07-07 20:11:50 +00:00
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.Subkeys = append(e.Subkeys, subkey)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AddEncryptionSubkey adds an encryption keypair as a subkey to the Entity.
|
|
|
|
|
// If config is nil, sensible defaults will be used.
|
|
|
|
|
func (e *Entity) AddEncryptionSubkey(config *packet.Config) error {
|
|
|
|
|
creationTime := config.Now()
|
|
|
|
|
keyLifetimeSecs := config.KeyLifetime()
|
2024-10-16 10:54:40 +00:00
|
|
|
|
return e.addEncryptionSubkey(config, creationTime, keyLifetimeSecs)
|
|
|
|
|
}
|
2022-07-07 20:11:50 +00:00
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
|
func (e *Entity) addEncryptionSubkey(config *packet.Config, creationTime time.Time, keyLifetimeSecs uint32) error {
|
2022-07-07 20:11:50 +00:00
|
|
|
|
subPrivRaw, err := newDecrypter(config)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
sub := packet.NewDecrypterPrivateKey(creationTime, subPrivRaw)
|
2024-10-16 10:54:40 +00:00
|
|
|
|
sub.IsSubkey = true
|
|
|
|
|
if config != nil && config.V5Keys {
|
|
|
|
|
sub.UpgradeToV5()
|
|
|
|
|
}
|
2022-07-07 20:11:50 +00:00
|
|
|
|
|
|
|
|
|
subkey := Subkey{
|
|
|
|
|
PublicKey: &sub.PublicKey,
|
|
|
|
|
PrivateKey: sub,
|
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
|
subkey.Sig = createSignaturePacket(e.PrimaryKey, packet.SigTypeSubkeyBinding, config)
|
|
|
|
|
subkey.Sig.CreationTime = creationTime
|
|
|
|
|
subkey.Sig.KeyLifetimeSecs = &keyLifetimeSecs
|
|
|
|
|
subkey.Sig.FlagsValid = true
|
|
|
|
|
subkey.Sig.FlagEncryptStorage = true
|
|
|
|
|
subkey.Sig.FlagEncryptCommunications = true
|
2022-07-07 20:11:50 +00:00
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
|
err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config)
|
|
|
|
|
if err != nil {
|
2022-07-07 20:11:50 +00:00
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.Subkeys = append(e.Subkeys, subkey)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generates a signing key
|
2024-10-16 10:54:40 +00:00
|
|
|
|
func newSigner(config *packet.Config) (signer interface{}, err error) {
|
2022-07-07 20:11:50 +00:00
|
|
|
|
switch config.PublicKeyAlgorithm() {
|
|
|
|
|
case packet.PubKeyAlgoRSA:
|
|
|
|
|
bits := config.RSAModulusBits()
|
|
|
|
|
if bits < 1024 {
|
|
|
|
|
return nil, errors.InvalidArgumentError("bits must be >= 1024")
|
|
|
|
|
}
|
|
|
|
|
if config != nil && len(config.RSAPrimes) >= 2 {
|
|
|
|
|
primes := config.RSAPrimes[0:2]
|
|
|
|
|
config.RSAPrimes = config.RSAPrimes[2:]
|
|
|
|
|
return generateRSAKeyWithPrimes(config.Random(), 2, bits, primes)
|
|
|
|
|
}
|
|
|
|
|
return rsa.GenerateKey(config.Random(), bits)
|
|
|
|
|
case packet.PubKeyAlgoEdDSA:
|
2024-10-16 10:54:40 +00:00
|
|
|
|
curve := ecc.FindEdDSAByGenName(string(config.CurveName()))
|
|
|
|
|
if curve == nil {
|
|
|
|
|
return nil, errors.InvalidArgumentError("unsupported curve")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
priv, err := eddsa.GenerateKey(config.Random(), curve)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return priv, nil
|
|
|
|
|
case packet.PubKeyAlgoECDSA:
|
|
|
|
|
curve := ecc.FindECDSAByGenName(string(config.CurveName()))
|
|
|
|
|
if curve == nil {
|
|
|
|
|
return nil, errors.InvalidArgumentError("unsupported curve")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
priv, err := ecdsa.GenerateKey(config.Random(), curve)
|
2022-07-07 20:11:50 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
|
return priv, nil
|
2022-07-07 20:11:50 +00:00
|
|
|
|
default:
|
|
|
|
|
return nil, errors.InvalidArgumentError("unsupported public key algorithm")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generates an encryption/decryption key
|
|
|
|
|
func newDecrypter(config *packet.Config) (decrypter interface{}, err error) {
|
|
|
|
|
switch config.PublicKeyAlgorithm() {
|
|
|
|
|
case packet.PubKeyAlgoRSA:
|
|
|
|
|
bits := config.RSAModulusBits()
|
|
|
|
|
if bits < 1024 {
|
|
|
|
|
return nil, errors.InvalidArgumentError("bits must be >= 1024")
|
|
|
|
|
}
|
|
|
|
|
if config != nil && len(config.RSAPrimes) >= 2 {
|
|
|
|
|
primes := config.RSAPrimes[0:2]
|
|
|
|
|
config.RSAPrimes = config.RSAPrimes[2:]
|
|
|
|
|
return generateRSAKeyWithPrimes(config.Random(), 2, bits, primes)
|
|
|
|
|
}
|
|
|
|
|
return rsa.GenerateKey(config.Random(), bits)
|
2024-10-16 10:54:40 +00:00
|
|
|
|
case packet.PubKeyAlgoEdDSA, packet.PubKeyAlgoECDSA:
|
|
|
|
|
fallthrough // When passing EdDSA or ECDSA, we generate an ECDH subkey
|
2022-07-07 20:11:50 +00:00
|
|
|
|
case packet.PubKeyAlgoECDH:
|
|
|
|
|
var kdf = ecdh.KDF{
|
|
|
|
|
Hash: algorithm.SHA512,
|
|
|
|
|
Cipher: algorithm.AES256,
|
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
|
curve := ecc.FindECDHByGenName(string(config.CurveName()))
|
|
|
|
|
if curve == nil {
|
|
|
|
|
return nil, errors.InvalidArgumentError("unsupported curve")
|
|
|
|
|
}
|
|
|
|
|
return ecdh.GenerateKey(config.Random(), curve, kdf)
|
2022-07-07 20:11:50 +00:00
|
|
|
|
default:
|
|
|
|
|
return nil, errors.InvalidArgumentError("unsupported public key algorithm")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var bigOne = big.NewInt(1)
|
|
|
|
|
|
|
|
|
|
// generateRSAKeyWithPrimes generates a multi-prime RSA keypair of the
|
|
|
|
|
// given bit size, using the given random source and prepopulated primes.
|
|
|
|
|
func generateRSAKeyWithPrimes(random io.Reader, nprimes int, bits int, prepopulatedPrimes []*big.Int) (*rsa.PrivateKey, error) {
|
|
|
|
|
priv := new(rsa.PrivateKey)
|
|
|
|
|
priv.E = 65537
|
|
|
|
|
|
|
|
|
|
if nprimes < 2 {
|
|
|
|
|
return nil, goerrors.New("generateRSAKeyWithPrimes: nprimes must be >= 2")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if bits < 1024 {
|
|
|
|
|
return nil, goerrors.New("generateRSAKeyWithPrimes: bits must be >= 1024")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
primes := make([]*big.Int, nprimes)
|
|
|
|
|
|
|
|
|
|
NextSetOfPrimes:
|
|
|
|
|
for {
|
|
|
|
|
todo := bits
|
|
|
|
|
// crypto/rand should set the top two bits in each prime.
|
|
|
|
|
// Thus each prime has the form
|
|
|
|
|
// p_i = 2^bitlen(p_i) × 0.11... (in base 2).
|
|
|
|
|
// And the product is:
|
|
|
|
|
// P = 2^todo × α
|
|
|
|
|
// where α is the product of nprimes numbers of the form 0.11...
|
|
|
|
|
//
|
|
|
|
|
// If α < 1/2 (which can happen for nprimes > 2), we need to
|
|
|
|
|
// shift todo to compensate for lost bits: the mean value of 0.11...
|
|
|
|
|
// is 7/8, so todo + shift - nprimes * log2(7/8) ~= bits - 1/2
|
|
|
|
|
// will give good results.
|
|
|
|
|
if nprimes >= 7 {
|
|
|
|
|
todo += (nprimes - 2) / 5
|
|
|
|
|
}
|
|
|
|
|
for i := 0; i < nprimes; i++ {
|
|
|
|
|
var err error
|
|
|
|
|
if len(prepopulatedPrimes) == 0 {
|
|
|
|
|
primes[i], err = rand.Prime(random, todo/(nprimes-i))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
primes[i] = prepopulatedPrimes[0]
|
|
|
|
|
prepopulatedPrimes = prepopulatedPrimes[1:]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
todo -= primes[i].BitLen()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure that primes is pairwise unequal.
|
|
|
|
|
for i, prime := range primes {
|
|
|
|
|
for j := 0; j < i; j++ {
|
|
|
|
|
if prime.Cmp(primes[j]) == 0 {
|
|
|
|
|
continue NextSetOfPrimes
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
n := new(big.Int).Set(bigOne)
|
|
|
|
|
totient := new(big.Int).Set(bigOne)
|
|
|
|
|
pminus1 := new(big.Int)
|
|
|
|
|
for _, prime := range primes {
|
|
|
|
|
n.Mul(n, prime)
|
|
|
|
|
pminus1.Sub(prime, bigOne)
|
|
|
|
|
totient.Mul(totient, pminus1)
|
|
|
|
|
}
|
|
|
|
|
if n.BitLen() != bits {
|
|
|
|
|
// This should never happen for nprimes == 2 because
|
|
|
|
|
// crypto/rand should set the top two bits in each prime.
|
|
|
|
|
// For nprimes > 2 we hope it does not happen often.
|
|
|
|
|
continue NextSetOfPrimes
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
priv.D = new(big.Int)
|
|
|
|
|
e := big.NewInt(int64(priv.E))
|
|
|
|
|
ok := priv.D.ModInverse(e, totient)
|
|
|
|
|
|
|
|
|
|
if ok != nil {
|
|
|
|
|
priv.Primes = primes
|
|
|
|
|
priv.N = n
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
priv.Precompute()
|
|
|
|
|
return priv, nil
|
|
|
|
|
}
|