use codec::{ConstEncodedLen, Decode, Encode, MaxEncodedLen};
use jam_types::{BoundedVec, OpaqueValidatorMetadata};
use rand_chacha_for_p256::{ChaCha12Rng, rand_core::SeedableRng};
use crate::{
InjectiveDecode, P256PeerId, PeerAddr, PeerDetails, PeerId, bandersnatch, ed25519,
hash_raw_concat,
};
#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen)]
pub struct ValidatorMetadata {
pub addr: PeerAddr,
pub p256_id: InjectiveDecode<P256PeerId, 33>,
pub rest1: [u8; 13],
pub rest2: [u32; 16],
}
impl ConstEncodedLen for ValidatorMetadata {}
impl Default for ValidatorMetadata {
fn default() -> Self {
Self {
addr: PeerAddr { ip: Default::default(), port: 0 },
p256_id: InjectiveDecode::Valid(P256PeerId::ZERO),
rest1: Default::default(),
rest2: Default::default(),
}
}
}
impl std::fmt::Debug for ValidatorMetadata {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug_struct = f.debug_struct("ValidatorMetadata");
debug_struct.field("addr", &self.addr);
if let InjectiveDecode::Valid(p256_id) = &self.p256_id {
debug_struct.field("p256_id", p256_id);
}
debug_struct.finish()
}
}
impl From<OpaqueValidatorMetadata> for ValidatorMetadata {
fn from(opaque: OpaqueValidatorMetadata) -> Self {
Self::decode(&mut &opaque[..])
.expect("ValidatorMetadata is carefully specified such that decode is infallible")
}
}
impl From<ValidatorMetadata> for OpaqueValidatorMetadata {
fn from(metadata: ValidatorMetadata) -> Self {
let mut opaque = OpaqueValidatorMetadata::default();
metadata.encode_to(&mut opaque.as_mut_slice());
opaque
}
}
#[cfg(any(test, feature = "rand"))]
impl rand::distr::Distribution<ValidatorMetadata> for rand::distr::StandardUniform {
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> ValidatorMetadata {
ValidatorMetadata {
addr: rng.random(),
p256_id: InjectiveDecode::Valid(rng.random()),
rest1: rng.random(),
rest2: rng.random(),
}
}
}
pub mod bls {
use codec::{Decode, Encode, MaxEncodedLen};
use jam_types::OpaqueBlsPublic;
pub const BLS_PUBLIC_SERIALIZED_LEN: usize = 144;
#[derive(Copy, Clone, Encode, Decode, Debug, Eq, PartialEq, MaxEncodedLen)]
pub struct Public(pub [u8; BLS_PUBLIC_SERIALIZED_LEN]);
impl Default for Public {
fn default() -> Self {
Public([0u8; BLS_PUBLIC_SERIALIZED_LEN])
}
}
impl From<OpaqueBlsPublic> for Public {
fn from(opaque: OpaqueBlsPublic) -> Self {
Self(opaque.0)
}
}
impl From<Public> for OpaqueBlsPublic {
fn from(public: Public) -> Self {
Self(public.0)
}
}
}
#[derive(Copy, Clone, Encode, Decode, Debug, Eq, PartialEq, MaxEncodedLen, Default)]
pub struct ValKeyset {
pub bandersnatch: bandersnatch::Public,
pub ed25519: ed25519::Public,
pub bls: bls::Public,
pub metadata: ValidatorMetadata,
}
impl From<&ValKeyset> for PeerDetails {
fn from(keyset: &ValKeyset) -> PeerDetails {
PeerDetails { id: PeerId(keyset.ed25519), addr: keyset.metadata.addr }
}
}
pub type ValKeysets = BoundedVec<ValKeyset, jam_types::MaxValCount>;
pub type EdKeys = BoundedVec<ed25519::Public, jam_types::MaxValCount>;
fn p256_secret_from_seed(seed: [u8; 32]) -> p256::ecdsa::SigningKey {
let mut rng = ChaCha12Rng::from_seed(seed);
p256::ecdsa::SigningKey::random(&mut rng)
}
pub struct SecretKeyset {
pub bandersnatch: bandersnatch::Secret,
pub ed25519: ed25519::Secret,
pub p256: p256::ecdsa::SigningKey,
}
impl SecretKeyset {
pub fn from_seed(seed: &[u8; 32]) -> Self {
Self {
bandersnatch: bandersnatch::Secret::from_seed(hash_raw_concat([
b"jam_val_key_bandersnatch".as_slice(),
seed.as_slice(),
])),
ed25519: ed25519::Secret::from_seed(hash_raw_concat([
b"jam_val_key_ed25519".as_slice(),
seed.as_slice(),
])),
p256: p256_secret_from_seed(hash_raw_concat([
b"jam_val_key_p256".as_slice(),
seed.as_slice(),
])),
}
}
pub fn trivial_seed(id: u32) -> [u8; 32] {
let mut seed = [0; 32];
for chunk in seed.chunks_mut(4) {
chunk.copy_from_slice(&id.to_le_bytes());
}
seed
}
pub fn trivial(id: u32) -> Self {
Self::from_seed(&Self::trivial_seed(id))
}
pub fn public(&self, mut metadata: ValidatorMetadata) -> ValKeyset {
metadata.p256_id = InjectiveDecode::Valid(self.p256.verifying_key().into());
ValKeyset {
ed25519: self.ed25519.public(),
bandersnatch: self.bandersnatch.public(),
bls: bls::Public::default(),
metadata,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use jam_types::hex::from_hex;
use rand::{Rng, RngCore};
#[test]
fn metadata_round_trip() {
assert_eq!(ValidatorMetadata::max_encoded_len(), OpaqueValidatorMetadata::LEN);
let mut rng = rand::rng();
for _ in 0..20 {
let mut opaque = OpaqueValidatorMetadata([0; 128]);
rng.fill_bytes(&mut opaque.0);
let metadata = ValidatorMetadata::from(opaque);
assert_eq!(OpaqueValidatorMetadata::from(metadata), opaque);
}
for _ in 0..20 {
let metadata: ValidatorMetadata = rand::rng().random();
let opaque = OpaqueValidatorMetadata::from(metadata);
assert_eq!(ValidatorMetadata::from(opaque), metadata);
}
}
#[test]
fn validator_keyset_order_assumptions() {
let keyset = SecretKeyset::trivial(0).public(Default::default());
let buf = keyset.encode();
let opaque = jam_types::OpaqueValKeyset::decode(&mut &buf[..]).unwrap();
assert_eq!(keyset.bandersnatch.encode(), opaque.bandersnatch[..]);
assert_eq!(keyset.ed25519.encode(), opaque.ed25519[..]);
assert_eq!(keyset.bls.encode(), opaque.bls[..]);
assert_eq!(keyset.metadata.encode(), opaque.metadata[..]);
let buf = opaque.encode();
let keyset2 = ValKeyset::decode(&mut &buf[..]).unwrap();
assert_eq!(keyset, keyset2);
}
#[test]
fn from_seed() {
let keyset = SecretKeyset::trivial(0).public(Default::default());
assert_eq!(
keyset.ed25519.as_bytes(),
from_hex("4418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499ace")
.unwrap()
.as_slice()
);
assert_eq!(
&keyset.bandersnatch.0,
from_hex("ff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b3")
.unwrap()
.as_slice()
);
assert_eq!(
keyset.metadata.p256_id.valid().unwrap().to_text().as_str(),
"oqov2a57d7etnpzb6aerv64y5j622ejkkvqjencdrwln4qhnoqvqb"
);
let keyset = SecretKeyset::trivial(1).public(Default::default());
assert_eq!(
keyset.ed25519.as_bytes(),
from_hex("ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d933")
.unwrap()
.as_slice()
);
assert_eq!(
&keyset.bandersnatch.0,
from_hex("dee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91")
.unwrap()
.as_slice()
);
assert_eq!(
keyset.metadata.p256_id.valid().unwrap().to_text().as_str(),
"ordkiwj4rcxzhxrh3xbfj6dyt3utrhrxukgy6xfi3fpubv4ioerzb"
);
let keyset = SecretKeyset::trivial(2).public(Default::default());
assert_eq!(
keyset.ed25519.as_bytes(),
from_hex("cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e341")
.unwrap()
.as_slice()
);
assert_eq!(
&keyset.bandersnatch.0,
from_hex("9326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66")
.unwrap()
.as_slice()
);
assert_eq!(
keyset.metadata.p256_id.valid().unwrap().to_text().as_str(),
"vnj7urb4lb75ronubjofyj4tuy4upjypgcnuktthjfm6ntoxb7qga"
);
let keyset = SecretKeyset::trivial(3).public(Default::default());
assert_eq!(
keyset.ed25519.as_bytes(),
from_hex("f30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d")
.unwrap()
.as_slice()
);
assert_eq!(
&keyset.bandersnatch.0,
from_hex("0746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981cc")
.unwrap()
.as_slice()
);
assert_eq!(
keyset.metadata.p256_id.valid().unwrap().to_text().as_str(),
"o3cucx6gy2ughphhb6iiy33cchj6hbwoc7kvp63r52yi4w22dbcnb"
);
let keyset = SecretKeyset::trivial(4).public(Default::default());
assert_eq!(
keyset.ed25519.as_bytes(),
from_hex("8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a")
.unwrap()
.as_slice()
);
assert_eq!(
&keyset.bandersnatch.0,
from_hex("151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b")
.unwrap()
.as_slice()
);
assert_eq!(
keyset.metadata.p256_id.valid().unwrap().to_text().as_str(),
"ozk7khb6idjpt3wuaikstki2ehgsi4ikne2fpsamdzimohihp4uyb"
);
let keyset = SecretKeyset::trivial(5).public(Default::default());
assert_eq!(
keyset.ed25519.as_bytes(),
from_hex("ab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c06")
.unwrap()
.as_slice()
);
assert_eq!(
&keyset.bandersnatch.0,
from_hex("2105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392e")
.unwrap()
.as_slice()
);
assert_eq!(
keyset.metadata.p256_id.valid().unwrap().to_text().as_str(),
"vgyzaaduuhipky3cuvwee4wr3eptbmsrg7zn7dlmrqxjmjlwwrgrb"
);
let keyset = SecretKeyset::from_seed(
from_hex("f92d680ea3f0ac06307795490d8a03c5c0d4572b5e0a8cffec87e1294855d9d1")
.unwrap()
.as_slice()
.try_into()
.unwrap(),
)
.public(Default::default());
assert_eq!(
keyset.ed25519.as_bytes(),
from_hex("11a695f674de95ff3daaff9a5b88c18448b10156bf88bc04200e48d5155c7243")
.unwrap()
.as_slice()
);
assert_eq!(
&keyset.bandersnatch.0,
from_hex("299bdfd8d615aadd9e6c58718f9893a5144d60e897bc9da1f3d73c935715c650")
.unwrap()
.as_slice()
);
assert_eq!(
keyset.metadata.p256_id.valid().unwrap().to_text().as_str(),
"vhoj24pkzgs4sszvzvpw6f76eeftvfmz2haepu7m7wlazqc6viwpa"
);
}
}