1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//! DKLs key material and signature types (in-memory).
use super::otext::{ExtReceiver, ExtSender};
use super::secp::{ProjectivePoint, Scalar};
use crate::tss::PartyId;
/// The two directions of OT-extension state between this party and one peer,
/// established at keygen and reused (under distinct sids) across signings.
#[derive(Clone)]
pub struct PairOTState {
/// OT-extension receiver, used when this party is Alice in a ΠMul.
pub as_alice: ExtReceiver,
/// OT-extension sender, used when this party is Bob in a ΠMul.
pub as_bob: ExtSender,
}
impl PairOTState {
/// Overwrites both directions' OT seeds and correlation Δ with zeros,
/// rendering this pair state unusable.
pub fn zeroize(&mut self) {
self.as_alice.zeroize();
self.as_bob.zeroize();
}
}
/// One party's output of the DKLs DKG: public material (joint key, per-party
/// commitments) and private material (Shamir share, per-pair OT state).
#[derive(Clone)]
pub struct Key {
/// Total number of parties.
pub n: usize,
/// Threshold (signing needs `t + 1` parties).
pub t: usize,
/// This party's 0-based index.
pub idx: usize,
/// All participants, sorted.
pub party_ids: Vec<PartyId>,
/// This party's Shamir secret share.
pub xi: Scalar,
/// Public commitments `BigXj[i] = x_i·G`.
pub big_xj: Vec<ProjectivePoint>,
/// Joint ECDSA public key `X`.
pub ecdsa_pub: ProjectivePoint,
/// Per-pair OT-extension state; `None` at `idx` (no self-pair).
pub ot: Vec<Option<PairOTState>>,
/// BIP32 master chain code (deterministic from the public key).
pub chain_code: [u8; 32],
}
impl Key {
/// Validates basic internal consistency.
pub fn validate_basic(&self) -> Result<(), super::Error> {
use super::Error::Validation;
if self.n == 0 || self.t >= self.n {
return Err(Validation(format!("invalid (N={}, T={})", self.n, self.t)));
}
if self.idx >= self.n {
return Err(Validation(format!("Idx={} out of range", self.idx)));
}
if self.party_ids.len() != self.n || self.big_xj.len() != self.n || self.ot.len() != self.n
{
return Err(Validation("inconsistent N-length fields".into()));
}
if bool::from(self.xi.is_zero()) {
return Err(Validation("Xi is zero".into()));
}
// Xi·G must equal BigXj[idx].
if !super::secp::point_eq(&super::secp::mul_base(&self.xi), &self.big_xj[self.idx]) {
return Err(Validation("Xi·G != BigXj[idx]".into()));
}
Ok(())
}
/// Overwrites the secret share with zero and scrubs every per-pair OT
/// state (PRG seeds and correlation Δ), rendering the key unusable for
/// signing or resharing. The chain code (public, but consistent with the
/// "unusable after zeroize" contract) is also cleared. Mirrors
/// [`crate::frosttss::Key::zeroize`].
///
/// Note: `purecrypto`'s secp256k1 `Scalar` wipes its limbs on drop, so the
/// old `xi` value is scrubbed when it is replaced here (and whenever a
/// `Key` is dropped); the OT byte arrays have no such drop behavior, hence
/// the explicit overwrite.
pub fn zeroize(&mut self) {
self.xi = Scalar::ZERO;
for pair in self.ot.iter_mut().flatten() {
pair.zeroize();
}
self.chain_code = [0u8; 32];
}
}
#[cfg(test)]
mod tests {
use super::super::{keygen, otext};
use purecrypto::rng::OsRng;
#[test]
fn zeroize_clears_share_and_ot_state() {
let ids = crate::tss::PartyId::sort(
(1..=2u8)
.map(|i| crate::tss::PartyId::new(i.to_string(), format!("P{i}"), vec![i]))
.collect(),
0,
);
let mut keys = keygen(2, 1, &ids, &mut OsRng).unwrap();
let mut key = keys.remove(0);
assert!(!bool::from(key.xi.is_zero()));
key.zeroize();
assert!(bool::from(key.xi.is_zero()));
assert_eq!(key.chain_code, [0u8; 32]);
for pair in key.ot.iter().flatten() {
assert_eq!(
pair.as_alice.to_bytes(),
vec![0u8; 2 * otext::KAPPA * otext::SEED_LEN]
);
assert_eq!(
pair.as_bob.to_bytes(),
vec![0u8; otext::DELTA_BYTES + otext::KAPPA * otext::SEED_LEN]
);
}
}
}
/// A DKLs ECDSA signature. `(r, s)` is canonical (low-S); `v` is the recovery
/// parity bit of `R.y`.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Signature {
/// Signature `r` component (big-endian 32 bytes).
pub r: Vec<u8>,
/// Signature `s` component (big-endian 32 bytes).
pub s: Vec<u8>,
/// Recovery parity bit.
pub v: u8,
}