use libsecp256k1::util::{TAG_PUBKEY_EVEN, TAG_PUBKEY_ODD};
use libsecp256k1::{
curve::{Affine, Field, Jacobian},
PublicKey, PublicKeyFormat,
};
use crate::error::DataUpdateError;
#[inline(always)]
pub fn affine_from_stored_secp_xy(x: &[u8; 32], y: &[u8; 32]) -> Result<Affine, DataUpdateError> {
if *x == [0u8; 32] || *y == [0u8; 32] {
return Err(DataUpdateError::InvalidAggregateSignature);
}
let mut fx = Field::default();
let mut fy = Field::default();
if !fx.set_b32(x) || !fy.set_b32(y) {
return Err(DataUpdateError::InvalidAggregateSignature);
}
let mut ge = Affine::default();
ge.set_xy(&fx, &fy);
Ok(ge)
}
#[derive(Default)]
pub struct CoalitionAccumulator {
jacobian: Jacobian,
has_point: bool,
}
impl CoalitionAccumulator {
#[inline(always)]
pub fn add_stored_xy(&mut self, x: &[u8; 32], y: &[u8; 32]) -> Result<(), DataUpdateError> {
let ge = affine_from_stored_secp_xy(x, y)?;
if !self.has_point {
self.jacobian = Jacobian::from_ge(&ge);
self.has_point = true;
} else {
self.jacobian = self.jacobian.add_ge(&ge);
}
Ok(())
}
#[inline(always)]
pub fn compressed_pubkey(&self) -> Result<[u8; 33], DataUpdateError> {
if !self.has_point {
return Err(DataUpdateError::InvalidAggregateSignature);
}
if self.jacobian.is_infinity() {
return Err(DataUpdateError::InvalidAggregateSignature);
}
let mut elem = Affine::from_gej(&self.jacobian);
elem.x.normalize_var();
elem.y.normalize_var();
let mut out = [0u8; 33];
let mut x_be = [0u8; 32];
elem.x.fill_b32(&mut x_be);
out[1..33].copy_from_slice(&x_be);
out[0] = if elem.y.is_odd() {
TAG_PUBKEY_ODD
} else {
TAG_PUBKEY_EVEN
};
Ok(out)
}
}
#[inline(always)]
pub fn public_key_from_affine_xy(
uncompressed_scratch: &mut [u8; 65],
x: &[u8; 32],
y: &[u8; 32],
) -> Result<PublicKey, DataUpdateError> {
if *x == [0u8; 32] || *y == [0u8; 32] {
return Err(DataUpdateError::InvalidAggregateSignature);
}
uncompressed_scratch[0] = 0x04;
uncompressed_scratch[1..33].copy_from_slice(x);
uncompressed_scratch[33..65].copy_from_slice(y);
PublicKey::parse_slice(uncompressed_scratch.as_ref(), Some(PublicKeyFormat::Full))
.map_err(|_| DataUpdateError::InvalidAggregateSignature)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn coalition_accumulator_matches_public_key_combine() {
let fixture_compressed: [[u8; 33]; 3] = [
[
0x03, 0xc0, 0x95, 0x27, 0xe9, 0x78, 0xf6, 0xea, 0x69, 0xf0, 0xc6, 0xb7, 0xac, 0x0f,
0xb6, 0x3a, 0xd0, 0x81, 0xa8, 0xa2, 0x91, 0x15, 0x1c, 0x5a, 0x0b, 0x11, 0x5c, 0xce,
0x43, 0x57, 0x51, 0xbe, 0x7d,
],
[
0x02, 0x64, 0xa7, 0x27, 0x04, 0xf3, 0x9f, 0x8d, 0xd1, 0x7f, 0x20, 0xd7, 0x1c, 0x5b,
0x21, 0xf3, 0x7b, 0x58, 0x52, 0x65, 0x6b, 0xc0, 0x55, 0x54, 0x42, 0xbf, 0x72, 0x72,
0x22, 0xf2, 0x9d, 0x7e, 0x58,
],
[
0x02, 0x75, 0xae, 0x1e, 0x3d, 0xac, 0x00, 0xeb, 0x7d, 0xf0, 0x2e, 0x9f, 0xe8, 0xd9,
0x70, 0x9c, 0x8a, 0x2c, 0x09, 0xa1, 0x1e, 0xd4, 0xf7, 0xd9, 0xaa, 0x46, 0xa7, 0xde,
0xa6, 0xcf, 0x37, 0x6d, 0x7f,
],
];
let pks: Vec<PublicKey> = fixture_compressed
.iter()
.map(|c| {
PublicKey::parse_slice(c, Some(PublicKeyFormat::Compressed))
.expect("fixture compressed key")
})
.collect();
let combined = PublicKey::combine(&pks).expect("combine");
let mut acc = CoalitionAccumulator::default();
for pk in &pks {
let full = pk.serialize();
let x: [u8; 32] = full[1..33].try_into().unwrap();
let y: [u8; 32] = full[33..65].try_into().unwrap();
acc.add_stored_xy(&x, &y).expect("accumulate");
}
assert_eq!(
acc.compressed_pubkey().expect("compressed"),
combined.serialize_compressed()
);
}
}