#![cfg(all(feature = "zf-ristretto255", feature = "std"))]
use curve25519_dalek::{ristretto::RistrettoPoint, scalar::Scalar};
use frostito::curve::{CurvePoint, CurveScalar};
use frost_core::{
keys::{KeyPackage, PublicKeyPackage, SigningShare, VerifyingShare},
round1::{Nonce, NonceCommitment, SigningCommitments, SigningNonces},
round2, Identifier, SigningPackage, VerifyingKey,
};
use frost_ristretto255::Ristretto255Sha512 as C;
use std::collections::BTreeMap;
use frostito::nested::{
aggregate_inner_commitment_pair, aggregate_inner_shares_verified, inner_commit,
inner_precommit, inner_sign, InnerCommitments,
NestedSigningRequest,
};
use frostito::{compute_lagrange_coefficients, Error, SecretShare};
use rand::rngs::OsRng;
type Point = RistrettoPoint;
const SESSION: [u8; 32] = [0x5Au8; 32];
fn id(i: u32) -> Identifier<C> {
(i as u16).try_into().unwrap()
}
fn vk(p: Point) -> VerifyingKey<C> {
VerifyingKey::new(p)
}
fn zf_commit(rng: &mut OsRng) -> (SigningNonces<C>, SigningCommitments<C>) {
let h = <Scalar as CurveScalar>::random(rng);
let b = <Scalar as CurveScalar>::random(rng);
let n = SigningNonces::from_nonces(Nonce::<C>::from_scalar(h), Nonce::<C>::from_scalar(b));
let c = *n.commitments();
(n, c)
}
fn zf_commitments(hiding: Point, binding: Point) -> SigningCommitments<C> {
SigningCommitments::new(NonceCommitment::new(hiding), NonceCommitment::new(binding))
}
fn zf_package(msg: &[u8], entries: Vec<(u32, SigningCommitments<C>)>) -> SigningPackage<C> {
SigningPackage::new(entries.into_iter().map(|(i, c)| (id(i), c)).collect(), msg)
}
fn zf_key_package(index: u32, share: &SecretShare<Scalar>, group: Point, t: u16) -> KeyPackage<C> {
KeyPackage::new(
id(index),
SigningShare::new(*share.scalar()),
VerifyingShare::new(<Point as CurvePoint>::generator().mul_scalar(share.scalar())),
vk(group),
t,
)
}
fn split(secret: &Scalar, n: u32, t: u32, rng: &mut OsRng) -> Vec<SecretShare<Scalar>> {
let mut coeffs = vec![*secret];
for _ in 1..t {
coeffs.push(<Scalar as CurveScalar>::random(rng));
}
(1..=n)
.map(|i| {
let x = <Scalar as CurveScalar>::from_u32(i);
let mut y = <Scalar as CurveScalar>::zero();
let mut xp = <Scalar as CurveScalar>::one();
for c in &coeffs {
y = y.add(&c.mul(&xp));
xp = xp.mul(&x);
}
SecretShare::new(i, y).unwrap()
})
.collect()
}
struct World {
group_pubkey: Point,
share_1: SecretShare<Scalar>,
inner_shares: Vec<SecretShare<Scalar>>,
quorum: Vec<u32>,
nested_public: Point,
}
fn world(rng: &mut OsRng) -> World {
let secret = <Scalar as CurveScalar>::random(rng);
let a1 = <Scalar as CurveScalar>::random(rng);
let eval = |x: u32| {
let xs = <Scalar as CurveScalar>::from_u32(x);
secret.add(&a1.mul(&xs))
};
let group_pubkey = <Point as CurvePoint>::generator().mul_scalar(&secret);
let sigma_2 = eval(2);
let nested_public = <Point as CurvePoint>::generator().mul_scalar(&sigma_2);
let pieces = split(&sigma_2, 5, 3, rng);
World {
group_pubkey,
share_1: SecretShare::new(1, eval(1)).unwrap(),
inner_shares: pieces[..3].to_vec(),
quorum: vec![1, 2, 3],
nested_public,
}
}
fn precommits(cs: &[InnerCommitments<Point>]) -> Vec<(u32, [u8; 32])> {
cs.iter()
.map(|c| (c.holder_index, inner_precommit::<Point>(c)))
.collect()
}
fn public_shares(shares: &[SecretShare<Scalar>]) -> Vec<(u32, Point)> {
shares
.iter()
.map(|s| {
(
s.index,
<Point as CurvePoint>::generator().mul_scalar(s.scalar()),
)
})
.collect()
}
#[test]
fn coordinator_cannot_swap_the_message_under_the_inner_group() {
let mut rng = OsRng;
let w = world(&mut rng);
const APPROVED: &[u8] = b"release 10 ZEC to alice";
const UNAPPROVED: &[u8] = b"release 10000 ZEC to mallory";
let mut nonces = Vec::new();
let mut commitments = Vec::new();
for &k in &w.quorum {
let (n, c) = inner_commit::<Point, _>(k, SESSION, &mut rng);
nonces.push(n);
commitments.push(c);
}
let (d_nested, e_nested) =
aggregate_inner_commitment_pair::<Point>(&SESSION, &precommits(&commitments), &commitments).unwrap();
let (nonces_1, commits_1) = zf_commit(&mut rng);
let commits_2 = zf_commitments(d_nested, e_nested);
let evil_package =
zf_package(UNAPPROVED, vec![(1, commits_1), (2, commits_2)]);
let pre = precommits(&commitments);
let request = NestedSigningRequest {
package: &evil_package,
nested_index: 2,
session_id: SESSION,
inner_precommits: &pre,
inner_commitments: &commitments,
active_indices: &w.quorum,
inner_threshold: 3,
};
for (n, s) in nonces.into_iter().zip(w.inner_shares.iter()) {
assert_eq!(
inner_sign::<C>(n, s, &vk(w.group_pubkey), APPROVED, &request).unwrap_err(),
Error::MessageMismatch,
"a holder must not sign a package over a message it did not approve"
);
}
let kp1 = zf_key_package(1, &w.share_1, w.group_pubkey, 2);
let _ = round2::sign(&evil_package, &nonces_1, &kp1).unwrap();
}
#[test]
fn the_approved_message_still_signs() {
let mut rng = OsRng;
let w = world(&mut rng);
const APPROVED: &[u8] = b"release 10 ZEC to alice";
let mut nonces = Vec::new();
let mut commitments = Vec::new();
for &k in &w.quorum {
let (n, c) = inner_commit::<Point, _>(k, SESSION, &mut rng);
nonces.push(n);
commitments.push(c);
}
let (d_nested, e_nested) =
aggregate_inner_commitment_pair::<Point>(&SESSION, &precommits(&commitments), &commitments).unwrap();
let (nonces_1, commits_1) = zf_commit(&mut rng);
let commits_2 = zf_commitments(d_nested, e_nested);
let package =
zf_package(APPROVED, vec![(1, commits_1), (2, commits_2)]);
let pre = precommits(&commitments);
let request = NestedSigningRequest {
package: &package,
nested_index: 2,
session_id: SESSION,
inner_precommits: &pre,
inner_commitments: &commitments,
active_indices: &w.quorum,
inner_threshold: 3,
};
let mut sigs = Vec::new();
for (n, s) in nonces.into_iter().zip(w.inner_shares.iter()) {
sigs.push(inner_sign::<C>(n, s, &vk(w.group_pubkey), APPROVED, &request).unwrap());
}
let params =
frostito::zf::inner_params_from_zf::<C>(&package, &vk(w.group_pubkey), 2).unwrap();
let z_nested = aggregate_inner_shares_verified::<Point>(
&sigs,
&commitments,
&public_shares(&w.inner_shares),
¶ms,
&w.quorum,
)
.expect("every share verifies");
let kp1 = zf_key_package(1, &w.share_1, w.group_pubkey, 2);
let sig_1 = round2::sign(&package, &nonces_1, &kp1).unwrap();
let sig_2 = frost_core::round2::SignatureShare::<C>::deserialize(
&<Scalar as CurveScalar>::to_bytes(&z_nested),
)
.unwrap();
let mut shares = BTreeMap::new();
shares.insert(id(1), sig_1);
shares.insert(id(2), sig_2);
let mut verifying = BTreeMap::new();
verifying.insert(id(1), VerifyingShare::new(<Point as CurvePoint>::generator().mul_scalar(w.share_1.scalar())));
verifying.insert(id(2), VerifyingShare::new(w.nested_public));
let pubkeys = PublicKeyPackage::<C>::new(verifying, vk(w.group_pubkey), Some(2));
let signature = frost_core::aggregate(&package, &shares, &pubkeys).unwrap();
assert!(pubkeys.verifying_key().verify(APPROVED, &signature).is_ok());
}
#[test]
fn substituted_nested_commitment_is_rejected_by_the_holder() {
let mut rng = OsRng;
let w = world(&mut rng);
let msg = b"settlement";
let mut nonces = Vec::new();
let mut commitments = Vec::new();
for &k in &w.quorum {
let (n, c) = inner_commit::<Point, _>(k, SESSION, &mut rng);
nonces.push(n);
commitments.push(c);
}
let (d_nested, e_nested) =
aggregate_inner_commitment_pair::<Point>(&SESSION, &precommits(&commitments), &commitments).unwrap();
let (_, foreign) = zf_commit(&mut rng);
assert_ne!(foreign.hiding().value(), d_nested);
assert_ne!(foreign.binding().value(), e_nested);
let (_, commits_1) = zf_commit(&mut rng);
let package =
zf_package(msg, vec![(1, commits_1), (2, foreign)]);
let pre = precommits(&commitments);
let request = NestedSigningRequest {
package: &package,
nested_index: 2,
session_id: SESSION,
inner_precommits: &pre,
inner_commitments: &commitments,
active_indices: &w.quorum,
inner_threshold: 3,
};
for (n, s) in nonces.into_iter().zip(w.inner_shares.iter()) {
assert_eq!(
inner_sign::<C>(n, s, &vk(w.group_pubkey), msg, &request).unwrap_err(),
Error::UnexpectedCommitment,
"the holder must notice that the outer package is not over its own round"
);
}
}
#[test]
fn nonces_from_another_session_are_rejected() {
let mut rng = OsRng;
let w = world(&mut rng);
let msg = b"settlement";
let mut nonces_a = Vec::new();
let mut commits_a = Vec::new();
for &k in &w.quorum {
let (n, c) = inner_commit::<Point, _>(k, SESSION, &mut rng);
nonces_a.push(n);
commits_a.push(c);
}
const OTHER: [u8; 32] = [0x11u8; 32];
let mut commits_b = Vec::new();
for &k in &w.quorum {
let (_, c) = inner_commit::<Point, _>(k, OTHER, &mut rng);
commits_b.push(c);
}
let (d_b, e_b) = aggregate_inner_commitment_pair::<Point>(&OTHER, &precommits(&commits_b), &commits_b).unwrap();
let (_, commits_1) = zf_commit(&mut rng);
let package = zf_package(msg, vec![(1, commits_1), (2, zf_commitments(d_b, e_b))]);
let pre = precommits(&commits_b);
let request = NestedSigningRequest {
package: &package,
nested_index: 2,
session_id: OTHER,
inner_precommits: &pre,
inner_commitments: &commits_b,
active_indices: &w.quorum,
inner_threshold: 3,
};
let n = nonces_a.remove(0);
assert_eq!(
inner_sign::<C>(n, &w.inner_shares[0], &vk(w.group_pubkey), msg, &request).unwrap_err(),
Error::SessionMismatch
);
let mut mixed = commits_b.clone();
mixed[0] = commits_a[0].clone();
assert_eq!(
aggregate_inner_commitment_pair::<Point>(&OTHER, &precommits(&mixed), &mixed).unwrap_err(),
Error::SessionMismatch
);
}
#[test]
fn incomplete_quorum_is_rejected() {
let mut rng = OsRng;
let w = world(&mut rng);
let msg = b"m";
let mut nonces = Vec::new();
let mut commitments = Vec::new();
for &k in &w.quorum {
let (n, c) = inner_commit::<Point, _>(k, SESSION, &mut rng);
nonces.push(n);
commitments.push(c);
}
let (d_nested, e_nested) =
aggregate_inner_commitment_pair::<Point>(&SESSION, &precommits(&commitments), &commitments).unwrap();
let (_, commits_1) = zf_commit(&mut rng);
let package = zf_package(msg, vec![(1, commits_1), (2, zf_commitments(d_nested, e_nested))]);
let pre = precommits(&commitments);
let request = NestedSigningRequest {
package: &package,
nested_index: 2,
session_id: SESSION,
inner_precommits: &pre,
inner_commitments: &commitments,
active_indices: &w.quorum,
inner_threshold: 3,
};
let params =
frostito::zf::inner_params_from_zf::<C>(&package, &vk(w.group_pubkey), 2).unwrap();
let mut sigs = Vec::new();
for (n, s) in nonces.into_iter().zip(w.inner_shares.iter()) {
sigs.push(inner_sign::<C>(n, s, &vk(w.group_pubkey), msg, &request).unwrap());
}
let pubs = public_shares(&w.inner_shares);
assert_eq!(
aggregate_inner_shares_verified::<Point>(
&sigs[..2],
&commitments,
&pubs,
¶ms,
&w.quorum,
)
.unwrap_err(),
vec![3],
"a missing holder must be named, not silently tolerated"
);
let dup = vec![
frostito::nested::InnerSignatureShare {
holder_index: sigs[0].holder_index,
response: sigs[0].response,
},
frostito::nested::InnerSignatureShare {
holder_index: sigs[0].holder_index,
response: sigs[0].response,
},
frostito::nested::InnerSignatureShare {
holder_index: sigs[1].holder_index,
response: sigs[1].response,
},
];
assert_eq!(
aggregate_inner_shares_verified::<Point>(&dup, &commitments, &pubs, ¶ms, &w.quorum)
.unwrap_err(),
vec![1, 3],
"a duplicated share and the still-missing holder are both named"
);
}
#[test]
fn response_equals_the_flat_frost_response() {
let mut rng = OsRng;
let secret = <Scalar as CurveScalar>::random(&mut rng);
let a1 = <Scalar as CurveScalar>::random(&mut rng);
let eval = |x: u32| {
let xs = <Scalar as CurveScalar>::from_u32(x);
secret.add(&a1.mul(&xs))
};
let group_pubkey = <Point as CurvePoint>::generator().mul_scalar(&secret);
let sigma_2 = eval(2);
let pieces = split(&sigma_2, 5, 3, &mut rng);
let quorum = vec![1u32, 2, 3];
let inner: Vec<SecretShare<Scalar>> = pieces[..3].to_vec();
let mut nonces = Vec::new();
let mut commitments = Vec::new();
for &k in &quorum {
let (n, c) = inner_commit::<Point, _>(k, SESSION, &mut rng);
nonces.push(n);
commitments.push(c);
}
let (d_nested, e_nested) =
aggregate_inner_commitment_pair::<Point>(&SESSION, &precommits(&commitments), &commitments).unwrap();
let (_, commits_1) = zf_commit(&mut rng);
let commits_2 = zf_commitments(d_nested, e_nested);
let package =
zf_package(b"m", vec![(1, commits_1), (2, commits_2)]);
let params = frostito::zf::inner_params_from_zf::<C>(&package, &vk(group_pubkey), 2).unwrap();
let pre = precommits(&commitments);
let request = NestedSigningRequest {
package: &package,
nested_index: 2,
session_id: SESSION,
inner_precommits: &pre,
inner_commitments: &commitments,
active_indices: &quorum,
inner_threshold: 3,
};
let mut sigs = Vec::new();
for (n, s) in nonces.into_iter().zip(inner.iter()) {
sigs.push(inner_sign::<C>(n, s, &vk(group_pubkey), b"m", &request).unwrap());
}
let mut z_nested = <Scalar as CurveScalar>::zero();
for s in &sigs {
z_nested = z_nested.add(&s.response);
}
let lhs = <Point as CurvePoint>::generator().mul_scalar(&z_nested);
let w = params.outer_lambda().mul(params.outer_challenge());
let rhs = d_nested
.add(&e_nested.mul_scalar(params.outer_binding()))
.add(&<Point as CurvePoint>::generator().mul_scalar(&sigma_2).mul_scalar(&w));
assert_eq!(lhs, rhs, "nested response is the flat FROST response");
let lag = compute_lagrange_coefficients::<Scalar>(&quorum).unwrap();
let mut recon = <Scalar as CurveScalar>::zero();
for (i, s) in inner.iter().enumerate() {
recon = recon.add(&lag[i].mul(s.scalar()));
}
assert_eq!(recon, sigma_2);
}
#[test]
fn a_malformed_quorum_is_rejected_by_the_signer() {
let mut rng = OsRng;
let w = world(&mut rng);
let cases: [(&[u32], u32, frostito::Error); 4] = [
(&[1, 2, 2], 3, frostito::Error::DuplicateIndex(2)),
(&[1, 2, 9], 3, frostito::Error::UnknownQuorumMember(9)),
(&[1, 2, 0], 3, frostito::Error::InvalidIndex),
(
&[1, 2],
3,
frostito::Error::InsufficientContributions { got: 2, need: 3 },
),
];
for (active, t, expected) in cases {
let mut nonces = Vec::new();
let mut commitments = Vec::new();
for &k in &w.quorum {
let (n, c) = inner_commit::<Point, _>(k, SESSION, &mut rng);
nonces.push(n);
commitments.push(c);
}
let (d_nested, e_nested) =
aggregate_inner_commitment_pair::<Point>(&SESSION, &precommits(&commitments), &commitments).unwrap();
let (_, commits_1) = zf_commit(&mut rng);
let commits_2 = zf_commitments(d_nested, e_nested);
let package =
zf_package(b"m", vec![(1, commits_1), (2, commits_2)]);
let pre = precommits(&commitments);
let request = NestedSigningRequest {
package: &package,
nested_index: 2,
session_id: SESSION,
inner_precommits: &pre,
inner_commitments: &commitments,
active_indices: active,
inner_threshold: t,
};
let err = inner_sign::<C>(
nonces.remove(0),
&w.inner_shares[0],
&vk(w.group_pubkey),
b"m",
&request,
)
.unwrap_err();
assert_eq!(err, expected, "quorum {:?} must be rejected", active);
}
}
#[test]
fn a_reveal_without_a_matching_precommit_is_rejected() {
let mut rng = OsRng;
let mut commitments = Vec::new();
for k in 1..=3u32 {
let (_, c) = inner_commit::<Point, _>(k, SESSION, &mut rng);
commitments.push(c);
}
let good = precommits(&commitments);
aggregate_inner_commitment_pair::<Point>(&SESSION, &good, &commitments).unwrap();
let (_, other) = inner_commit::<Point, _>(2, SESSION, &mut rng);
let mut swapped = good.clone();
swapped[1] = (2, frostito::nested::inner_precommit::<Point>(&other));
assert_eq!(
aggregate_inner_commitment_pair::<Point>(&SESSION, &swapped, &commitments).unwrap_err(),
Error::PrecommitMismatch(2),
"a reveal that does not match its precommitment names the holder"
);
let missing: Vec<(u32, [u8; 32])> = good.iter().copied().filter(|(k, _)| *k != 3).collect();
assert_eq!(
aggregate_inner_commitment_pair::<Point>(&SESSION, &missing, &commitments).unwrap_err(),
Error::PrecommitMismatch(3)
);
let mut extra = good.clone();
extra.push((9, [0u8; 32]));
aggregate_inner_commitment_pair::<Point>(&SESSION, &extra, &commitments).unwrap();
}
#[test]
fn a_spent_session_cannot_sign_twice_across_a_restore() {
use frostito::nested::{MemorySpentSessions, SpentSessions};
use frostito::signer::{Holder, SignRequest, Signer, Spend, Stack};
let mut rng = OsRng;
let w = world(&mut rng);
let mut nonces = Vec::new();
let mut commitments = Vec::new();
for &k in &w.quorum {
let (n, c) = inner_commit::<Point, _>(k, SESSION, &mut rng);
nonces.push(n);
commitments.push(c);
}
let pre = precommits(&commitments);
let (d_nested, e_nested) =
aggregate_inner_commitment_pair::<Point>(&SESSION, &pre, &commitments).unwrap();
let (_, commits_1) = zf_commit(&mut rng);
let commits_2 = zf_commitments(d_nested, e_nested);
let package =
zf_package(b"m", vec![(1, commits_1), (2, commits_2)]);
let request = NestedSigningRequest {
package: &package,
nested_index: 2,
session_id: SESSION,
inner_precommits: &pre,
inner_commitments: &commitments,
active_indices: &w.quorum,
inner_threshold: 3,
};
let store = MemorySpentSessions::new();
assert!(!store.is_spent(&SESSION, 1));
let key = vk(w.group_pubkey);
let mut signer = Stack::new(Holder::new(&w.inner_shares[0], &key))
.layer(Spend::new(store))
.into_inner();
signer
.sign(SignRequest::raw(nonces.remove(0), b"m", &request))
.expect("the first share is produced normally");
assert!(signer.store().is_spent(&SESSION, 1));
let (restored, restored_c) = inner_commit::<Point, _>(1, SESSION, &mut rng);
let mut restored_commitments = commitments.clone();
restored_commitments[0] = restored_c;
let restored_pre = precommits(&restored_commitments);
let (rd, re) = aggregate_inner_commitment_pair::<Point>(
&SESSION,
&restored_pre,
&restored_commitments,
)
.unwrap();
let (_, rc1) = zf_commit(&mut rng);
let restored_package = zf_package(b"m", vec![(1, rc1), (2, zf_commitments(rd, re))]);
let restored_request = NestedSigningRequest {
package: &restored_package,
nested_index: 2,
session_id: SESSION,
inner_precommits: &restored_pre,
inner_commitments: &restored_commitments,
active_indices: &w.quorum,
inner_threshold: 3,
};
assert_eq!(
signer
.sign(SignRequest::raw(restored, b"m", &restored_request))
.unwrap_err(),
Error::SessionSpent,
"a restored node must not answer the same session twice"
);
let store = signer.into_store();
assert!(!store.is_spent(&SESSION, 2));
let mut signer_2 = Stack::new(Holder::new(&w.inner_shares[1], &key))
.layer(Spend::new(store))
.into_inner();
signer_2
.sign(SignRequest::raw(nonces.remove(0), b"m", &request))
.expect("holder 2 has not signed this session");
assert_eq!(signer_2.into_store().len(), 2);
}
#[test]
fn a_dishonest_inner_holder_is_named() {
use frostito::nested::split_evaluation_for_inner;
let mut rng = OsRng;
let msg = b"m";
let sigma_2 = <Scalar as CurveScalar>::random(&mut rng);
let quorum: Vec<u32> = vec![1, 2, 3];
let (pieces, _) = split_evaluation_for_inner::<Point, _>(&sigma_2, 5, 3, &mut rng);
let inner: Vec<SecretShare<Scalar>> = quorum
.iter()
.map(|k| {
let (_, piece) = pieces.iter().find(|(i, _)| i == k).unwrap();
SecretShare::new(*k, *piece).unwrap()
})
.collect();
let mut nonces = Vec::new();
let mut commitments = Vec::new();
for &k in &quorum {
let (n, c) = inner_commit::<Point, _>(k, SESSION, &mut rng);
nonces.push(n);
commitments.push(c);
}
let pre = precommits(&commitments);
let (d_nested, e_nested) =
aggregate_inner_commitment_pair::<Point>(&SESSION, &pre, &commitments).unwrap();
let group_pubkey = <Point as CurvePoint>::generator().mul_scalar(&sigma_2);
let (_, commits_1) = zf_commit(&mut rng);
let package = zf_package(msg, vec![(1, commits_1), (2, zf_commitments(d_nested, e_nested))]);
let params =
frostito::zf::inner_params_from_zf::<C>(&package, &vk(group_pubkey), 2).unwrap();
let request = NestedSigningRequest {
package: &package,
nested_index: 2,
session_id: SESSION,
inner_precommits: &pre,
inner_commitments: &commitments,
active_indices: &quorum,
inner_threshold: 3,
};
let mut sigs = Vec::new();
for (n, share) in nonces.into_iter().zip(inner.iter()) {
sigs.push(inner_sign::<C>(n, share, &vk(group_pubkey), msg, &request).unwrap());
}
sigs[1].response = sigs[1].response.add(&<Scalar as CurveScalar>::one());
let err = aggregate_inner_shares_verified::<Point>(
&sigs,
&commitments,
&public_shares(&inner),
¶ms,
&quorum,
)
.expect_err("a tampered share must be rejected");
assert_eq!(err, vec![2], "the cheating holder must be identified");
}
#[test]
fn a_stack_spends_and_the_request_binds() {
use frostito::nested::MemorySpentSessions;
use frostito::signer::{Holder, SignRequest, Signer, Spend, Stack};
use frostito::SigningContext;
let mut rng = OsRng;
let w = world(&mut rng);
let ctx = SigningContext::new(7, [0x5a; 32], b"release the escrow");
let bound = ctx.encode();
let mut nonces = Vec::new();
let mut commitments = Vec::new();
for &k in &w.quorum {
let (n, c) = inner_commit::<Point, _>(k, SESSION, &mut rng);
nonces.push(n);
commitments.push(c);
}
let pre = precommits(&commitments);
let (d, e) = aggregate_inner_commitment_pair::<Point>(&SESSION, &pre, &commitments).unwrap();
let (_, commits_1) = zf_commit(&mut rng);
let package = zf_package(&bound, vec![(1, commits_1), (2, zf_commitments(d, e))]);
let request = NestedSigningRequest {
package: &package,
nested_index: 2,
session_id: SESSION,
inner_precommits: &pre,
inner_commitments: &commitments,
active_indices: &w.quorum,
inner_threshold: 3,
};
let log = MemorySpentSessions::new();
let share = &w.inner_shares[0];
let key = vk(w.group_pubkey);
let mut signer = Stack::new(Holder::new(share, &key))
.layer(Spend::new(log))
.into_inner();
let req = SignRequest::bound(nonces.remove(0), &ctx, &request);
assert_eq!(req.approved_message(), bound.as_slice());
assert_ne!(req.approved_message(), b"release the escrow");
let first = signer.sign(req);
assert!(first.is_ok(), "the composed stack must sign: {first:?}");
assert_eq!(package.message(), bound.as_slice());
let (n_raw, _) = inner_commit::<Point, _>(w.quorum[0], SESSION, &mut rng);
let mut unspent = Stack::new(Holder::new(share, &key))
.layer(Spend::new(MemorySpentSessions::new()))
.into_inner();
assert_eq!(
unspent
.sign(SignRequest::raw(n_raw, b"release the escrow", &request))
.unwrap_err(),
frostito::Error::MessageMismatch,
"an unbound message must not pass for a bound one"
);
let (n2, _) = inner_commit::<Point, _>(w.quorum[0], SESSION, &mut rng);
assert_eq!(
signer
.sign(SignRequest::bound(n2, &ctx, &request))
.unwrap_err(),
frostito::Error::SessionSpent,
"the spend layer must refuse a second answer for the same session"
);
}