use crate::errors::DecodeError;
use crate::{tagged_hashes, BinaryEncoding};
use secp::{MaybePoint, MaybeScalar, Point, Scalar, G};
use sha2::Digest as _;
pub const SEC_NONCE_SIZE: usize = 97;
pub const PUB_NONCE_SIZE: usize = 66;
pub struct NonceSeed(pub [u8; 32]);
impl From<[u8; 32]> for NonceSeed {
fn from(bytes: [u8; 32]) -> Self {
NonceSeed(bytes)
}
}
impl From<&[u8; 32]> for NonceSeed {
fn from(bytes: &[u8; 32]) -> Self {
NonceSeed(*bytes)
}
}
#[cfg(any(test, feature = "rand"))]
impl<T: rand::RngCore + rand::CryptoRng> From<&mut T> for NonceSeed {
fn from(rng: &mut T) -> NonceSeed {
let mut bytes = [0u8; 32];
rng.try_fill_bytes(&mut bytes)
.expect("error generating secure secret nonce seed");
NonceSeed(bytes)
}
}
pub(crate) fn xor_bytes<const SIZE: usize>(a: &[u8; SIZE], b: &[u8; SIZE]) -> [u8; SIZE] {
let mut out = [0; SIZE];
for i in 0..SIZE {
out[i] = a[i] ^ b[i]
}
out
}
fn extra_input_length_check<T: AsRef<[u8]>>(extra_inputs: &[T]) {
let total_len: usize = extra_inputs
.iter()
.map(|extra_input| extra_input.as_ref().len())
.sum();
assert!(
total_len <= u32::MAX as usize,
"excessive use of extra_input when building secnonce; max length is 2^32 bytes"
);
}
#[derive(Clone, Default)]
pub struct SecNonceSpices<'ns> {
pub(crate) seckey: Option<Scalar>,
pub(crate) message: Option<&'ns dyn AsRef<[u8]>>,
pub(crate) extra_inputs: Vec<&'ns dyn AsRef<[u8]>>,
}
impl<'ns> SecNonceSpices<'ns> {
pub fn new() -> SecNonceSpices<'ns> {
SecNonceSpices::default()
}
pub fn with_seckey(self, seckey: impl Into<Scalar>) -> SecNonceSpices<'ns> {
SecNonceSpices {
seckey: Some(seckey.into()),
..self
}
}
pub fn with_message<M: AsRef<[u8]>>(self, message: &'ns M) -> SecNonceSpices<'ns> {
SecNonceSpices {
message: Some(message),
..self
}
}
pub fn with_extra_input<E: AsRef<[u8]>>(mut self, extra_input: &'ns E) -> SecNonceSpices<'ns> {
self.extra_inputs.push(extra_input);
extra_input_length_check(&self.extra_inputs);
self
}
}
pub struct SecNonceBuilder<'snb> {
nonce_seed_bytes: [u8; 32],
seckey: Option<Scalar>,
pubkey: Point,
aggregated_pubkey: Option<Point>,
message: Option<&'snb [u8]>,
extra_inputs: Vec<&'snb dyn AsRef<[u8]>>,
}
impl<'snb> SecNonceBuilder<'snb> {
#[doc = "https://medium.com/blockstream/musig-dn-schnorr-multisignatures\
-with-verifiably-deterministic-nonces-27424b5df9d6#e3b6)"]
pub fn from_pubkey(
nonce_seed: impl Into<NonceSeed>,
pubkey: impl Into<Point>,
) -> SecNonceBuilder<'snb> {
let NonceSeed(nonce_seed_bytes) = nonce_seed.into();
SecNonceBuilder {
nonce_seed_bytes,
pubkey: pubkey.into(),
seckey: None,
aggregated_pubkey: None,
message: None,
extra_inputs: Vec::new(),
}
}
#[doc = "https://medium.com/blockstream/musig-dn-schnorr-multisignatures\
-with-verifiably-deterministic-nonces-27424b5df9d6#e3b6)"]
pub fn from_seckey(
nonce_seed: impl Into<NonceSeed>,
seckey: impl Into<Scalar>,
) -> SecNonceBuilder<'snb> {
let NonceSeed(nonce_seed_bytes) = nonce_seed.into();
let seckey = seckey.into();
SecNonceBuilder {
nonce_seed_bytes,
pubkey: seckey * G,
seckey: Some(seckey),
aggregated_pubkey: None,
message: None,
extra_inputs: Vec::new(),
}
}
pub fn with_message<M: AsRef<[u8]>>(self, msg: &'snb M) -> SecNonceBuilder<'snb> {
SecNonceBuilder {
message: Some(msg.as_ref()),
..self
}
}
pub fn with_aggregated_pubkey(
self,
aggregated_pubkey: impl Into<Point>,
) -> SecNonceBuilder<'snb> {
SecNonceBuilder {
aggregated_pubkey: Some(aggregated_pubkey.into()),
..self
}
}
pub fn with_extra_input<E: AsRef<[u8]>>(
mut self,
extra_input: &'snb E,
) -> SecNonceBuilder<'snb> {
self.extra_inputs.push(extra_input);
extra_input_length_check(&self.extra_inputs);
self
}
pub fn with_spices(mut self, spices: SecNonceSpices<'snb>) -> SecNonceBuilder<'snb> {
if let Some(seckey) = spices.seckey {
self.seckey = Some(seckey);
self.pubkey = seckey * G;
}
self.message = spices.message.map(|msg| msg.as_ref()).or(self.message);
let mut new_extra_inputs = spices.extra_inputs;
self.extra_inputs.append(&mut new_extra_inputs);
extra_input_length_check(&self.extra_inputs);
self
}
pub fn build(self) -> SecNonce {
let rand = match self.seckey {
Some(seckey) => {
let nonce_seed_hash: [u8; 32] = tagged_hashes::MUSIG_AUX_TAG_HASHER
.clone()
.chain_update(self.nonce_seed_bytes)
.finalize()
.into();
xor_bytes(&seckey.serialize(), &nonce_seed_hash)
}
None => self.nonce_seed_bytes,
};
let mut hasher = tagged_hashes::MUSIG_NONCE_TAG_HASHER
.clone()
.chain_update(rand)
.chain_update([33]) .chain_update(self.pubkey.serialize());
match self.aggregated_pubkey {
None => hasher.update([0]),
Some(aggregated_pubkey) => {
hasher.update([32]); hasher.update(aggregated_pubkey.serialize_xonly());
}
};
match self.message {
None => hasher.update([0]),
Some(message) => {
hasher.update([1]);
hasher.update((message.len() as u64).to_be_bytes());
hasher.update(message);
}
};
let extra_input_total_len: usize = self
.extra_inputs
.iter()
.map(|extra_in| extra_in.as_ref().len())
.sum();
hasher.update((extra_input_total_len as u32).to_be_bytes());
for extra_input in self.extra_inputs {
hasher.update(extra_input.as_ref());
}
let hash1 = <[u8; 32]>::from(hasher.clone().chain_update([0]).finalize());
let hash2 = <[u8; 32]>::from(hasher.clone().chain_update([1]).finalize());
let k1 = match MaybeScalar::reduce_from(&hash1) {
MaybeScalar::Zero => Scalar::one(),
MaybeScalar::Valid(k) => k,
};
let k2 = match MaybeScalar::reduce_from(&hash2) {
MaybeScalar::Zero => Scalar::one(),
MaybeScalar::Valid(k) => k,
};
SecNonce {
k1,
k2,
pubkey: self.pubkey,
}
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct SecNonce {
pub(crate) k1: Scalar,
pub(crate) k2: Scalar,
pub(crate) pubkey: Point,
}
impl SecNonce {
pub fn new<T: Into<Scalar>>(k1: T, k2: T, pubkey: impl Into<Point>) -> SecNonce {
SecNonce {
k1: k1.into(),
k2: k2.into(),
pubkey: pubkey.into(),
}
}
pub fn build_with_pubkey<'snb>(
nonce_seed: impl Into<NonceSeed>,
pubkey: impl Into<Point>,
) -> SecNonceBuilder<'snb> {
SecNonceBuilder::from_pubkey(nonce_seed, pubkey)
}
pub fn build_with_seckey<'snb>(
nonce_seed: impl Into<NonceSeed>,
seckey: impl Into<Scalar>,
) -> SecNonceBuilder<'snb> {
SecNonceBuilder::from_seckey(nonce_seed, seckey)
}
pub fn generate(
nonce_seed: impl Into<NonceSeed>,
seckey: impl Into<Scalar>,
aggregated_pubkey: impl Into<Point>,
message: impl AsRef<[u8]>,
extra_input: impl AsRef<[u8]>,
) -> SecNonce {
Self::build_with_seckey(nonce_seed, seckey)
.with_aggregated_pubkey(aggregated_pubkey)
.with_message(&message)
.with_extra_input(&extra_input)
.build()
}
#[cfg(any(test, feature = "rand"))]
pub fn random<R>(rng: &mut R, pubkey: impl Into<Point>) -> SecNonce
where
R: rand::RngCore + rand::CryptoRng,
{
SecNonce {
k1: Scalar::random(rng),
k2: Scalar::random(rng),
pubkey: pubkey.into(),
}
}
pub fn public_nonce(&self) -> PubNonce {
PubNonce {
R1: self.k1 * G,
R2: self.k2 * G,
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, Hash, Ord, PartialOrd)]
pub struct PubNonce {
#[allow(missing_docs)]
pub R1: Point,
#[allow(missing_docs)]
pub R2: Point,
}
impl PubNonce {
pub fn new<T: Into<Point>>(R1: T, R2: T) -> PubNonce {
PubNonce {
R1: R1.into(),
R2: R2.into(),
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, Hash, Ord, PartialOrd)]
pub struct AggNonce {
#[allow(missing_docs)]
pub R1: MaybePoint,
#[allow(missing_docs)]
pub R2: MaybePoint,
}
impl AggNonce {
pub fn new<T: Into<MaybePoint>>(R1: T, R2: T) -> AggNonce {
AggNonce {
R1: R1.into(),
R2: R2.into(),
}
}
pub fn sum<T, I>(nonces: I) -> AggNonce
where
T: std::borrow::Borrow<PubNonce>,
I: IntoIterator<Item = T>,
{
let (r1s, r2s): (Vec<Point>, Vec<Point>) = nonces
.into_iter()
.map(|pubnonce| (pubnonce.borrow().R1, pubnonce.borrow().R2))
.unzip();
AggNonce {
R1: Point::sum(r1s),
R2: Point::sum(r2s),
}
}
pub fn nonce_coefficient<S>(
&self,
aggregated_pubkey: impl Into<Point>,
message: impl AsRef<[u8]>,
) -> S
where
S: From<MaybeScalar>,
{
let hash: [u8; 32] = tagged_hashes::MUSIG_NONCECOEF_TAG_HASHER
.clone()
.chain_update(self.R1.serialize())
.chain_update(self.R2.serialize())
.chain_update(aggregated_pubkey.into().serialize_xonly())
.chain_update(message.as_ref())
.finalize()
.into();
S::from(MaybeScalar::reduce_from(&hash))
}
pub fn final_nonce<P>(&self, nonce_coeff: impl Into<MaybeScalar>) -> P
where
P: From<Point>,
{
let nonce_coeff: MaybeScalar = nonce_coeff.into();
let aggnonce_sum = self.R1 + (nonce_coeff * self.R2);
P::from(match aggnonce_sum {
MaybePoint::Infinity => Point::generator(),
MaybePoint::Valid(p) => p,
})
}
}
mod encodings {
use super::*;
impl BinaryEncoding for SecNonce {
type Serialized = [u8; SEC_NONCE_SIZE];
fn to_bytes(&self) -> Self::Serialized {
let mut serialized = [0u8; SEC_NONCE_SIZE];
serialized[..32].clone_from_slice(&self.k1.serialize());
serialized[32..64].clone_from_slice(&self.k2.serialize());
serialized[64..].clone_from_slice(&self.pubkey.serialize());
serialized
}
fn from_bytes(bytes: &[u8]) -> Result<Self, DecodeError<Self>> {
if bytes.len() != SEC_NONCE_SIZE {
return Err(DecodeError::bad_length(bytes.len()));
}
let k1 = Scalar::from_slice(&bytes[..32])?;
let k2 = Scalar::from_slice(&bytes[32..64])?;
let pubkey = Point::from_slice(&bytes[64..])?;
Ok(SecNonce { k1, k2, pubkey })
}
}
impl BinaryEncoding for PubNonce {
type Serialized = [u8; PUB_NONCE_SIZE];
fn to_bytes(&self) -> Self::Serialized {
let mut bytes = [0u8; PUB_NONCE_SIZE];
bytes[..PUB_NONCE_SIZE / 2].clone_from_slice(&self.R1.serialize());
bytes[PUB_NONCE_SIZE / 2..].clone_from_slice(&self.R2.serialize());
bytes
}
fn from_bytes(bytes: &[u8]) -> Result<Self, DecodeError<Self>> {
if bytes.len() != PUB_NONCE_SIZE {
return Err(DecodeError::bad_length(bytes.len()));
}
let R1 = Point::from_slice(&bytes[..PUB_NONCE_SIZE / 2])?;
let R2 = Point::from_slice(&bytes[PUB_NONCE_SIZE / 2..])?;
Ok(PubNonce { R1, R2 })
}
}
impl BinaryEncoding for AggNonce {
type Serialized = [u8; PUB_NONCE_SIZE];
fn to_bytes(&self) -> Self::Serialized {
let mut serialized = [0u8; PUB_NONCE_SIZE];
serialized[..33].clone_from_slice(&self.R1.serialize());
serialized[33..].clone_from_slice(&self.R2.serialize());
serialized
}
fn from_bytes(bytes: &[u8]) -> Result<Self, DecodeError<Self>> {
if bytes.len() != PUB_NONCE_SIZE {
return Err(DecodeError::bad_length(bytes.len()));
}
let R1 = MaybePoint::from_slice(&bytes[..PUB_NONCE_SIZE / 2])?;
let R2 = MaybePoint::from_slice(&bytes[PUB_NONCE_SIZE / 2..])?;
Ok(AggNonce { R1, R2 })
}
}
impl_encoding_traits!(SecNonce, SEC_NONCE_SIZE);
impl_encoding_traits!(PubNonce, PUB_NONCE_SIZE);
impl_encoding_traits!(AggNonce, PUB_NONCE_SIZE);
impl_hex_display!(PubNonce);
impl_hex_display!(AggNonce);
}
impl<P> std::iter::Sum<P> for AggNonce
where
P: std::borrow::Borrow<PubNonce>,
{
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = P>,
{
let refs = iter.collect::<Vec<P>>();
AggNonce::sum(refs.iter().map(|nonce| nonce.borrow()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{testhex, KeyAggContext};
#[test]
fn test_nonce_generation() {
const NONCE_GEN_VECTORS: &[u8] = include_bytes!("test_vectors/nonce_gen_vectors.json");
#[derive(serde::Deserialize)]
struct NonceGenTestCase {
#[serde(rename = "rand_", deserialize_with = "testhex::deserialize")]
nonce_seed: [u8; 32],
#[serde(rename = "sk")]
seckey: Option<Scalar>,
#[serde(rename = "pk")]
pubkey: Point,
#[serde(rename = "aggpk", deserialize_with = "testhex::deserialize_option")]
aggregated_pubkey: Option<[u8; 32]>,
#[serde(rename = "msg", deserialize_with = "testhex::deserialize_option")]
message: Option<Vec<u8>>,
#[serde(rename = "extra_in", deserialize_with = "testhex::deserialize_option")]
extra_input: Option<Vec<u8>>,
expected_secnonce: SecNonce,
expected_pubnonce: PubNonce,
}
#[derive(serde::Deserialize)]
struct NonceGenVectors {
test_cases: Vec<NonceGenTestCase>,
}
let vectors: NonceGenVectors = serde_json::from_slice(NONCE_GEN_VECTORS)
.expect("failed to parse test vectors from nonce_gen_vectors.json");
for test_case in vectors.test_cases {
if let Some(seckey) = test_case.seckey {
assert_eq!(
test_case.pubkey,
seckey.base_point_mul(),
"nonce generation vector has inconsistent sk and pk"
);
}
let mut secnonce_builder = match test_case.seckey {
Some(sk) => SecNonce::build_with_seckey(test_case.nonce_seed, sk),
None => SecNonce::build_with_pubkey(test_case.nonce_seed, test_case.pubkey),
};
if let Some(aggregated_pubkey) = test_case.aggregated_pubkey {
let aggregated_pubkey = Point::lift_x(&aggregated_pubkey).unwrap_or_else(|_| {
panic!(
"invalid aggregated xonly pubkey in test vector: {}",
base16ct::lower::encode_string(&aggregated_pubkey)
)
});
secnonce_builder = secnonce_builder.with_aggregated_pubkey(aggregated_pubkey);
}
if let Some(ref message) = test_case.message {
secnonce_builder = secnonce_builder.with_message(message);
}
if let Some(ref extra_input) = test_case.extra_input {
secnonce_builder = secnonce_builder.with_extra_input(extra_input);
}
let secnonce = secnonce_builder.build();
assert_eq!(secnonce, test_case.expected_secnonce);
assert_eq!(secnonce.public_nonce(), test_case.expected_pubnonce);
}
}
#[test]
fn secnonce_spices_seckey_replaces_derived_pubkey() {
let first_seckey = Scalar::try_from([0x11; 32]).unwrap();
let second_seckey = Scalar::try_from([0x22; 32]).unwrap();
let secnonce = SecNonce::build_with_seckey([0xAA; 32], first_seckey)
.with_spices(SecNonceSpices::new().with_seckey(second_seckey))
.build();
assert_eq!(secnonce.pubkey, second_seckey.base_point_mul());
}
#[test]
fn secnonce_spices_seckey_replaces_explicit_pubkey() {
let explicit_seckey = Scalar::try_from([0x11; 32]).unwrap();
let spice_seckey = Scalar::try_from([0x22; 32]).unwrap();
let explicit_pubkey = explicit_seckey.base_point_mul();
let spice_pubkey = spice_seckey.base_point_mul();
let secnonce = SecNonce::build_with_pubkey([0xAA; 32], explicit_pubkey)
.with_spices(SecNonceSpices::new().with_seckey(spice_seckey))
.build();
assert_eq!(secnonce.pubkey, spice_pubkey);
}
#[test]
fn test_nonce_aggregation() {
const NONCE_AGG_VECTORS: &[u8] = include_bytes!("test_vectors/nonce_agg_vectors.json");
#[derive(serde::Deserialize)]
struct NonceAggError {
signer: usize,
}
#[derive(serde::Deserialize)]
struct NonceAggErrorTestCase {
#[serde(rename = "pnonce_indices")]
public_nonce_indexes: Vec<usize>,
error: NonceAggError,
}
#[derive(serde::Deserialize)]
struct ValidNonceAggTestCase {
#[serde(rename = "pnonce_indices")]
public_nonce_indexes: Vec<usize>,
#[serde(rename = "expected")]
aggregated_nonce: AggNonce,
}
#[derive(serde::Deserialize)]
struct NonceAggTestVectors {
#[serde(deserialize_with = "testhex::deserialize_vec", rename = "pnonces")]
public_nonces: Vec<Vec<u8>>,
valid_test_cases: Vec<ValidNonceAggTestCase>,
error_test_cases: Vec<NonceAggErrorTestCase>,
}
let vectors: NonceAggTestVectors = serde_json::from_slice(NONCE_AGG_VECTORS)
.expect("failed to parse test vectors from nonce_agg_vectors.json");
for test_case in vectors.valid_test_cases {
let nonces: Vec<PubNonce> = test_case
.public_nonce_indexes
.into_iter()
.map(|i| {
PubNonce::from_bytes(&vectors.public_nonces[i]).unwrap_or_else(|_| {
panic!(
"used invalid nonce in valid test case: {}",
base16ct::lower::encode_string(&vectors.public_nonces[i])
)
})
})
.collect();
let aggregated_nonce = AggNonce::sum(&nonces);
assert_eq!(aggregated_nonce, test_case.aggregated_nonce);
}
for test_case in vectors.error_test_cases {
for (signer_index, i) in test_case.public_nonce_indexes.into_iter().enumerate() {
let nonce_result = PubNonce::try_from(vectors.public_nonces[i].as_slice());
if signer_index == test_case.error.signer {
assert_eq!(
nonce_result,
Err(DecodeError::from(secp::errors::InvalidPointBytes))
);
} else {
nonce_result.unwrap_or_else(|_| {
panic!("unexpected pub nonce parsing error for signer {}", i)
});
}
}
}
}
#[test]
fn nonce_reuse_demo() {
let alice_seckey = Scalar::try_from([0x11; 32]).unwrap();
let bob_seckey = Scalar::try_from([0x22; 32]).unwrap();
let alice_pubkey = alice_seckey * G;
let bob_pubkey = bob_seckey * G;
let key_agg_ctx = KeyAggContext::new([alice_pubkey, bob_pubkey]).unwrap();
let message = b"you betta not sign this twice";
let alice_secnonce = SecNonceBuilder::from_pubkey([0xAA; 32], alice_pubkey).build();
let bob_secnonce_1 = SecNonceBuilder::from_pubkey([0xB1; 32], bob_pubkey).build();
let bob_secnonce_2 = SecNonceBuilder::from_pubkey([0xB2; 32], bob_pubkey).build();
let bob_secnonce_3 = SecNonceBuilder::from_pubkey([0xB3; 32], bob_pubkey).build();
let aggnonce_1 =
AggNonce::sum([alice_secnonce.public_nonce(), bob_secnonce_1.public_nonce()]);
let s1: MaybeScalar = crate::sign_partial(
&key_agg_ctx,
alice_seckey,
alice_secnonce.clone(),
&aggnonce_1,
message,
)
.unwrap();
let aggnonce_2 =
AggNonce::sum([alice_secnonce.public_nonce(), bob_secnonce_2.public_nonce()]);
let s2: MaybeScalar = crate::sign_partial(
&key_agg_ctx,
alice_seckey,
alice_secnonce.clone(),
&aggnonce_2,
message,
)
.unwrap();
let aggnonce_3 =
AggNonce::sum([alice_secnonce.public_nonce(), bob_secnonce_3.public_nonce()]);
let s3: MaybeScalar = crate::sign_partial(
&key_agg_ctx,
alice_seckey,
alice_secnonce.clone(),
&aggnonce_3,
message,
)
.unwrap();
let a = key_agg_ctx.key_coefficient(alice_pubkey).unwrap();
let aggregated_pubkey: Point = key_agg_ctx.aggregated_pubkey();
let b1: MaybeScalar = aggnonce_1.nonce_coefficient(aggregated_pubkey, message);
let b2: MaybeScalar = aggnonce_2.nonce_coefficient(aggregated_pubkey, message);
let b3: MaybeScalar = aggnonce_3.nonce_coefficient(aggregated_pubkey, message);
let final_nonce_1: Point = aggnonce_1.final_nonce(b1);
let final_nonce_2: Point = aggnonce_2.final_nonce(b2);
let final_nonce_3: Point = aggnonce_3.final_nonce(b3);
let e1: MaybeScalar = crate::compute_challenge_hash_tweak(
&final_nonce_1.serialize_xonly(),
&key_agg_ctx.aggregated_pubkey(),
message,
);
let e2: MaybeScalar = crate::compute_challenge_hash_tweak(
&final_nonce_2.serialize_xonly(),
&key_agg_ctx.aggregated_pubkey(),
message,
);
let e3: MaybeScalar = crate::compute_challenge_hash_tweak(
&final_nonce_3.serialize_xonly(),
&key_agg_ctx.aggregated_pubkey(),
message,
);
let one = MaybeScalar::Valid(Scalar::one());
let r1 = one.negate_if(final_nonce_1.parity());
let r2 = one.negate_if(final_nonce_2.parity());
let r3 = one.negate_if(final_nonce_3.parity());
let v1 = r1 * b1;
let v2 = r2 * b2;
let v3 = r3 * b3;
let w1 = e1 * a;
let w2 = e2 * a;
let w3 = e3 * a;
let det = r1 * (v2 * w3 - w2 * v3) - v1 * (r2 * w3 - w2 * r3) + w1 * (r2 * v3 - v2 * r3);
let det_key =
r1 * (v2 * s3 - s2 * v3) - v1 * (r2 * s3 - s2 * r3) + s1 * (r2 * v3 - v2 * r3);
let extracted_d = (det_key / det.unwrap()).unwrap();
let extracted_key =
extracted_d.negate_if(aggregated_pubkey.parity() ^ key_agg_ctx.parity_acc);
assert_eq!(extracted_key, alice_seckey);
}
}