Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store decompressed BlsPublicKeys in IndexedDB #3174

Merged
merged 6 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion bls/src/lazy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,12 @@ impl LazyPublicKey {

impl From<PublicKey> for LazyPublicKey {
fn from(key: PublicKey) -> LazyPublicKey {
LazyPublicKey(cache().intern(&key.compress(), OnceLock::from(Some(key))))
let result = LazyPublicKey(cache().intern(&key.compress(), OnceLock::from(Some(key))));
// TODO: This might block while another thread computes the decompression. :/
//
// Needs an addition in the Rust standard library to fix.
let _ = result.0.value().set(Some(key));
result
}
}

Expand Down
4 changes: 4 additions & 0 deletions bls/src/types/compressed_public_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ impl CompressedPublicKey {

/// Transforms the compressed form back into the projective form.
pub fn uncompress(&self) -> Result<PublicKey, Error> {
log::info!(
compressed = &self.to_hex()[..16],
"decompressing BLS public key",
);
let affine_point: G2Affine =
CanonicalDeserialize::deserialize_compressed(&mut &self.public_key[..])
.map_err(|e| Error::new(ErrorKind::Other, e))?;
Expand Down
44 changes: 41 additions & 3 deletions bls/src/types/public_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup, Group};
use ark_ff::Zero;
pub use ark_mnt6_753::G2Projective;
use ark_mnt6_753::{G1Projective, MNT6_753};
use ark_serialize::CanonicalSerialize;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use log::error;
use nimiq_hash::Hash;

Expand All @@ -19,6 +19,11 @@ pub struct PublicKey {
}

impl PublicKey {
/// Size of the trusted serialization.
///
/// See [`PublicKey::trusted_deserialize`].
pub const TRUSTED_SERIALIZATION_SIZE: usize = 570;
nibhar marked this conversation as resolved.
Show resolved Hide resolved

/// Generates a public key from a given point in G2. This function will produce an error if it is given the point at infinity.
pub fn new(public_key: G2Projective) -> Self {
if public_key.is_zero() {
Expand Down Expand Up @@ -60,8 +65,9 @@ impl PublicKey {
/// and one bit indicating if it is the "point-at-infinity".
pub fn compress(&self) -> CompressedPublicKey {
let mut buffer = [0u8; CompressedPublicKey::SIZE];
CanonicalSerialize::serialize_compressed(&self.public_key.into_affine(), &mut buffer[..])
.unwrap();
let affine = self.public_key.into_affine();
assert_eq!(affine.compressed_size(), buffer.len());
affine.serialize_compressed(&mut buffer[..]).unwrap();
nibhar marked this conversation as resolved.
Show resolved Hide resolved
CompressedPublicKey { public_key: buffer }
}

Expand All @@ -72,6 +78,38 @@ impl PublicKey {
let public_key = self.public_key.mul_bigint([x as u64]);
PublicKey { public_key }
}

/// Serialization to trusted storage.
///
/// If in doubt, use the default serialization, or even better, the default
/// serialization of [`LazyPublicKey`](crate::lazy::LazyPublicKey) instead.
///
/// See [`PublicKey::trusted_deserialize`] for details.
pub fn trusted_serialize(&self) -> [u8; PublicKey::TRUSTED_SERIALIZATION_SIZE] {
hrxi marked this conversation as resolved.
Show resolved Hide resolved
let mut result = [0u8; PublicKey::TRUSTED_SERIALIZATION_SIZE];
assert_eq!(self.public_key.uncompressed_size(), result.len());
self.public_key
.serialize_uncompressed(&mut result[..])
.unwrap();
result
}

/// Deserialization from trusted storage.
///
/// **This does not check whether the resulting key is on the curve.** This
/// means that you can only use this serialization when you trust the
/// creator of that serialization completely.
///
/// If in doubt, use the default serialization, or even better, the default
/// serialization of [`LazyPublicKey`](crate::lazy::LazyPublicKey) instead.
pub fn trusted_deserialize(
hrxi marked this conversation as resolved.
Show resolved Hide resolved
serialized: &[u8; PublicKey::TRUSTED_SERIALIZATION_SIZE],
) -> PublicKey {
PublicKey {
public_key: CanonicalDeserialize::deserialize_uncompressed_unchecked(&serialized[..])
.unwrap(),
}
}
}

impl Eq for PublicKey {}
Expand Down
14 changes: 14 additions & 0 deletions bls/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,17 @@ fn aggregate_signatures_serialization() {
&AggregateSignature::deserialize_from_vec(&ser_agg_sig).unwrap()
));
}

#[test]
fn trusted_serialization() {
let hex_public_key = "ae4cc2e31e04add9a6d379b4379b02f302971503cbac8d02fdc5d2dc8204d24ec8d095627d037de747f1a8ea7bf3c1693262d947f78e0cc73c18ecc2f2ec5b2249d551e1680fe0c973a7951bd78d4fbe0326be71286ed34004d2443eb3b00167a02edffcfd2b8539448fa116c5454da2d181dc03ea8cfe3fedb58b9b945d5e506c794deb3ba73983005b3ff799212bf59030a8dd17ff48fd5d015695195a022fed8ba4fab28a4c3e2d6f41be0e6315e41824df161219c02be5a281c215011c13131184187e9100d2d6a5321fd9b154806ecc78e93b91331a5334b8876fd1b8ea62b17ce6045fc9e1af60b7705b0cf86dba79f5bcb8320c99a45f3b7c7178f8f87ba953de2755c61af882059c1de1d7a35357f06cd4a7d954e4bb211900";

let raw_public_key: Vec<u8> = hex::decode(hex_public_key).unwrap();
let compressed_public_key = CompressedPublicKey::deserialize_from_vec(&raw_public_key).unwrap();

let public_key = compressed_public_key.uncompress().unwrap();
assert_eq!(
PublicKey::trusted_deserialize(&public_key.trusted_serialize()).public_key,
public_key.public_key,
);
}
37 changes: 17 additions & 20 deletions genesis-builder/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
#[macro_use]
extern crate log;

use std::{
fs::{read_to_string, OpenOptions},
io::Error as IoError,
path::Path,
};
use std::{fs, io::Error as IoError, path::Path};

use nimiq_account::{
Account, Accounts, BasicAccount, HashedTimeLockedContract, StakingContract,
Expand Down Expand Up @@ -217,7 +213,7 @@ impl GenesisBuilder {
///
/// See `genesis/src/genesis/unit-albatross.toml` for an example.
pub fn from_config_file<P: AsRef<Path>>(path: P) -> Result<Self, GenesisBuilderError> {
Self::from_config(toml::from_str(&read_to_string(path)?)?)
Self::from_config(toml::from_str(&fs::read_to_string(path)?)?)
}

pub fn from_config(config: config::GenesisConfig) -> Result<Self, GenesisBuilderError> {
Expand Down Expand Up @@ -647,6 +643,8 @@ impl GenesisBuilder {
db: MdbxDatabase,
directory: P,
) -> Result<(Blake2bHash, bool), GenesisBuilderError> {
let directory = directory.as_ref();

let GenesisInfo {
block,
hash,
Expand All @@ -658,25 +656,24 @@ impl GenesisBuilder {
debug!("Accounts:");
debug!(?accounts);

let block_path = directory.as_ref().join("block.dat");
let block_path = directory.join("block.dat");
info!(path = %block_path.display(), "Writing block to");
let mut file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(&block_path)?;
block.serialize_to_writer(&mut file)?;
fs::write(block_path, block.serialize_to_vec())?;

{
let decompressed_path = directory.join("decompressed_keys.dat");
let mut decompressed = Vec::new();
for key in block.validators().expect("must be election").voting_keys() {
decompressed.extend_from_slice(&key.trusted_serialize());
}
fs::write(decompressed_path, decompressed)?;
}

let have_accounts = accounts.is_some();
if let Some(accounts) = accounts {
let accounts_path = directory.as_ref().join("accounts.dat");
let accounts_path = directory.join("accounts.dat");
info!(path = %accounts_path.display(), "Writing accounts to");
let mut file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(&accounts_path)?;
accounts.serialize_to_writer(&mut file)?;
fs::write(accounts_path, accounts.serialize_to_vec())?;
}

Ok((hash, have_accounts))
Expand Down
1 change: 1 addition & 0 deletions genesis/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ fn write_genesis_rs(directory: &Path, name: &str, genesis_hash: &Blake2bHash, ha
let genesis_rs = format!(
r#"GenesisData {{
block: include_bytes!(concat!(env!("OUT_DIR"), "/genesis/{name}/block.dat")),
decompressed_keys: include_bytes!(concat!(env!("OUT_DIR"), "/genesis/{name}/decompressed_keys.dat")),
hash: Blake2bHash([{hash}]),
accounts: {accounts_expr},
}}"#,
Expand Down
28 changes: 27 additions & 1 deletion genesis/src/networks.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::env;
#[cfg(feature = "genesis-override")]
use std::path::Path;
use std::{env, sync::OnceLock};

use nimiq_block::Block;
use nimiq_bls::{LazyPublicKey as BlsLazyPublicKey, PublicKey as BlsPublicKey};
#[cfg(feature = "genesis-override")]
use nimiq_database::mdbx::MdbxDatabase;
#[cfg(feature = "genesis-override")]
Expand All @@ -17,6 +18,7 @@ use nimiq_serde::Serialize;
#[derive(Clone, Debug)]
struct GenesisData {
block: &'static [u8],
decompressed_keys: &'static [u8],
hash: Blake2bHash,
accounts: Option<&'static [u8]>,
}
Expand Down Expand Up @@ -81,16 +83,40 @@ fn read_genesis_config(config: &Path) -> Result<GenesisData, GenesisBuilderError

Ok(GenesisData {
block: Box::leak(block.into_boxed_slice()),
decompressed_keys: &[],
hash,
accounts: accounts.map(|accounts| Box::leak(accounts.into_boxed_slice()) as &'static _),
})
}

static KEYS_DEV: OnceLock<Box<[BlsLazyPublicKey]>> = OnceLock::new();
static KEYS_TEST: OnceLock<Box<[BlsLazyPublicKey]>> = OnceLock::new();
static KEYS_UNIT: OnceLock<Box<[BlsLazyPublicKey]>> = OnceLock::new();
static KEYS_MAIN: OnceLock<Box<[BlsLazyPublicKey]>> = OnceLock::new();

fn network(network_id: NetworkId) -> Option<&'static NetworkInfo> {
let result = network_impl(network_id);
if let Some(info) = result {
assert_eq!(network_id, info.network_id);
assert_eq!(network_id, info.genesis_block().network());
let keys = match network_id {
NetworkId::DevAlbatross => &KEYS_DEV,
NetworkId::TestAlbatross => &KEYS_TEST,
NetworkId::UnitAlbatross => &KEYS_UNIT,
NetworkId::MainAlbatross => &KEYS_MAIN,
_ => unreachable!(),
};
keys.get_or_init(|| {
info.genesis
.decompressed_keys
.chunks(BlsPublicKey::TRUSTED_SERIALIZATION_SIZE)
Eligioo marked this conversation as resolved.
Show resolved Hide resolved
.map(|chunk| {
BlsLazyPublicKey::from(BlsPublicKey::trusted_deserialize(
&chunk.try_into().unwrap(),
))
})
.collect()
});
}
result
}
Expand Down
4 changes: 4 additions & 0 deletions web-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ workspace = true
crate-type = ["rlib", "cdylib"]

[dependencies]
ark-serialize = "0.4"
futures = { workspace = true }
gloo-timers = { version = "0.3", features = ["futures"] }
hex = "0.4"
idb = "0.6.4"
js-sys = "0.3"
log = { workspace = true }
rand_core = "0.6.4"
serde = "1.0"
serde_bytes = "0.11"
serde_json = "1.0"
serde-wasm-bindgen = "0.6"
tokio = { version = "1.43", features = ["sync"] }
tsify = { git = "https://github.com/sisou/tsify", branch = "sisou/comments", default-features = false, features = ["js"] }
Expand Down
Loading
Loading