mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Strongly type EmbeddedEllipticCurveKeys
Adds a signed variant to validate knowledge and ownership. Add SCALE derivations for `EmbeddedEllipticCurveKeys`
This commit is contained in:
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -10259,10 +10259,13 @@ dependencies = [
|
|||||||
"ciphersuite 0.4.2",
|
"ciphersuite 0.4.2",
|
||||||
"dalek-ff-group",
|
"dalek-ff-group",
|
||||||
"dkg-musig",
|
"dkg-musig",
|
||||||
|
"embedwards25519",
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
"scale-info",
|
"scale-info",
|
||||||
|
"schnorr-signatures",
|
||||||
"schnorrkel",
|
"schnorrkel",
|
||||||
|
"secq256k1",
|
||||||
"sp-core",
|
"sp-core",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ scale-info = { version = "2", default-features = false, features = ["derive"], o
|
|||||||
sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "74839cba4a7f48023080215e5194fd6ab7e270e5", default-features = false }
|
sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "74839cba4a7f48023080215e5194fd6ab7e270e5", default-features = false }
|
||||||
|
|
||||||
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["alloc"] }
|
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["alloc"] }
|
||||||
|
schnorr-signatures = { path = "../../crypto/schnorr", default-features = false }
|
||||||
dalek-ff-group = { path = "../../crypto/dalek-ff-group", default-features = false, features = ["alloc"] }
|
dalek-ff-group = { path = "../../crypto/dalek-ff-group", default-features = false, features = ["alloc"] }
|
||||||
|
embedwards25519 = { path = "../../crypto/embedwards25519", default-features = false, features = ["alloc"] }
|
||||||
|
secq256k1 = { path = "../../crypto/secq256k1", default-features = false, features = ["alloc"] }
|
||||||
dkg = { package = "dkg-musig", path = "../../crypto/dkg/musig", default-features = false }
|
dkg = { package = "dkg-musig", path = "../../crypto/dkg/musig", default-features = false }
|
||||||
|
|
||||||
schnorrkel = { version = "0.11", default-features = false }
|
schnorrkel = { version = "0.11", default-features = false }
|
||||||
@@ -36,7 +39,22 @@ bech32 = { version = "0.11", default-features = false }
|
|||||||
rand_core = { version = "0.6", default-features = false, features = ["std"] }
|
rand_core = { version = "0.6", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = ["zeroize/std", "borsh/std", "bitvec/std", "scale?/std", "scale-info?/std", "sp-core/std", "ciphersuite/std", "dalek-ff-group/std", "dkg/std", "schnorrkel/std", "bech32/std"]
|
std = [
|
||||||
|
"zeroize/std",
|
||||||
|
"borsh/std",
|
||||||
|
"bitvec/std",
|
||||||
|
"scale?/std",
|
||||||
|
"scale-info?/std",
|
||||||
|
"sp-core/std",
|
||||||
|
"ciphersuite/std",
|
||||||
|
"schnorr-signatures/std",
|
||||||
|
"dalek-ff-group/std",
|
||||||
|
"embedwards25519/std",
|
||||||
|
"secq256k1/std",
|
||||||
|
"dkg/std",
|
||||||
|
"schnorrkel/std",
|
||||||
|
"bech32/std"
|
||||||
|
]
|
||||||
serde = []
|
serde = []
|
||||||
non_canonical_scale_derivations = ["scale", "scale-info"]
|
non_canonical_scale_derivations = ["scale", "scale-info"]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
|||||||
@@ -3,6 +3,16 @@ use borsh::{BorshSerialize, BorshDeserialize};
|
|||||||
|
|
||||||
use sp_core::{ConstU32, bounded::BoundedVec};
|
use sp_core::{ConstU32, bounded::BoundedVec};
|
||||||
|
|
||||||
|
use ciphersuite::{
|
||||||
|
group::{ff::FromUniformBytes, GroupEncoding},
|
||||||
|
Ciphersuite,
|
||||||
|
};
|
||||||
|
use embedwards25519::Embedwards25519;
|
||||||
|
use secq256k1::Secq256k1;
|
||||||
|
use schnorr_signatures::SchnorrSignature;
|
||||||
|
|
||||||
|
use crate::network_id::ExternalNetworkId;
|
||||||
|
|
||||||
/// A Ristretto public key.
|
/// A Ristretto public key.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -89,7 +99,7 @@ impl Zeroize for ExternalKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ExternalKey {
|
impl ExternalKey {
|
||||||
/// The maximum length for am external key.
|
/// The maximum length for an external key.
|
||||||
/*
|
/*
|
||||||
This support keys up to 96 bytes (such as BLS12-381 G2, which is the largest elliptic-curve
|
This support keys up to 96 bytes (such as BLS12-381 G2, which is the largest elliptic-curve
|
||||||
group element we might reasonably use as a key). This can always be increased if we need to
|
group element we might reasonably use as a key). This can always be increased if we need to
|
||||||
@@ -100,12 +110,236 @@ impl ExternalKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Key(s) on embedded elliptic curve(s).
|
/// Key(s) on embedded elliptic curve(s).
|
||||||
///
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
/// This may be a single key if the external network uses the same embedded elliptic curve as
|
pub enum EmbeddedEllipticCurveKeys {
|
||||||
/// used for the key to oraclize onto Serai. Else, it'll be a key on the embedded elliptic curve
|
/// The embedded elliptic curve keys for a Bitcoin validator.
|
||||||
/// used for the key to oraclize onto Serai concatenated with the key on the embedded elliptic
|
Bitcoin(
|
||||||
/// curve used for the external network.
|
<<Embedwards25519 as Ciphersuite>::G as GroupEncoding>::Repr,
|
||||||
pub type EmbeddedEllipticCurveKeys = BoundedVec<u8, ConstU32<{ 2 * ExternalKey::MAX_LEN }>>;
|
<<Secq256k1 as Ciphersuite>::G as GroupEncoding>::Repr,
|
||||||
|
),
|
||||||
|
/// The embedded elliptic curve keys for an Ethereum validator.
|
||||||
|
Ethereum(
|
||||||
|
<<Embedwards25519 as Ciphersuite>::G as GroupEncoding>::Repr,
|
||||||
|
<<Secq256k1 as Ciphersuite>::G as GroupEncoding>::Repr,
|
||||||
|
),
|
||||||
|
/// The embedded elliptic curve key for a Monero validator.
|
||||||
|
Monero(<<Embedwards25519 as Ciphersuite>::G as GroupEncoding>::Repr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EmbeddedEllipticCurveKeys {
|
||||||
|
/// The network these keys are for.
|
||||||
|
pub fn network(&self) -> ExternalNetworkId {
|
||||||
|
match self {
|
||||||
|
Self::Bitcoin(_, _) => ExternalNetworkId::Bitcoin,
|
||||||
|
Self::Ethereum(_, _) => ExternalNetworkId::Ethereum,
|
||||||
|
Self::Monero(_) => ExternalNetworkId::Monero,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "non_canonical_scale_derivations")]
|
||||||
|
impl scale::Encode for EmbeddedEllipticCurveKeys {
|
||||||
|
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
|
||||||
|
match self {
|
||||||
|
EmbeddedEllipticCurveKeys::Bitcoin(e, s) | EmbeddedEllipticCurveKeys::Ethereum(e, s) => {
|
||||||
|
let mut res = [0; 66];
|
||||||
|
res[0] = self.network() as u8;
|
||||||
|
res[1 .. 33].copy_from_slice(e);
|
||||||
|
res[33 ..].copy_from_slice(s);
|
||||||
|
f(&res)
|
||||||
|
}
|
||||||
|
EmbeddedEllipticCurveKeys::Monero(e) => {
|
||||||
|
let mut res = [0; 33];
|
||||||
|
res[0] = self.network() as u8;
|
||||||
|
res[1 ..].copy_from_slice(e);
|
||||||
|
f(&res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "non_canonical_scale_derivations")]
|
||||||
|
impl scale::MaxEncodedLen for EmbeddedEllipticCurveKeys {
|
||||||
|
fn max_encoded_len() -> usize {
|
||||||
|
66
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "non_canonical_scale_derivations")]
|
||||||
|
impl scale::EncodeLike<EmbeddedEllipticCurveKeys> for EmbeddedEllipticCurveKeys {}
|
||||||
|
#[cfg(feature = "non_canonical_scale_derivations")]
|
||||||
|
impl scale::Decode for EmbeddedEllipticCurveKeys {
|
||||||
|
fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
|
||||||
|
let network_id = ExternalNetworkId::decode(&mut *input)?;
|
||||||
|
let embedwards25519 =
|
||||||
|
<<Embedwards25519 as Ciphersuite>::G as GroupEncoding>::Repr::decode(&mut *input)?;
|
||||||
|
Ok(match network_id {
|
||||||
|
ExternalNetworkId::Bitcoin => {
|
||||||
|
let secq256k1 = <[u8; 33]>::decode(&mut *input)?;
|
||||||
|
EmbeddedEllipticCurveKeys::Bitcoin(embedwards25519, secq256k1.into())
|
||||||
|
}
|
||||||
|
ExternalNetworkId::Ethereum => {
|
||||||
|
let secq256k1 = <[u8; 33]>::decode(&mut *input)?;
|
||||||
|
EmbeddedEllipticCurveKeys::Ethereum(embedwards25519, secq256k1.into())
|
||||||
|
}
|
||||||
|
ExternalNetworkId::Monero => EmbeddedEllipticCurveKeys::Monero(embedwards25519),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "non_canonical_scale_derivations")]
|
||||||
|
impl scale::DecodeWithMemTracking for EmbeddedEllipticCurveKeys {}
|
||||||
|
|
||||||
|
/// Key(s) on embedded elliptic curve(s) with the required proofs of knowledge.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
||||||
|
pub enum SignedEmbeddedEllipticCurveKeys {
|
||||||
|
/// The signed embedded elliptic curve keys for a Bitcoin validator.
|
||||||
|
Bitcoin(
|
||||||
|
<<Embedwards25519 as Ciphersuite>::G as GroupEncoding>::Repr,
|
||||||
|
<<Secq256k1 as Ciphersuite>::G as GroupEncoding>::Repr,
|
||||||
|
[u8; 64],
|
||||||
|
[u8; 65],
|
||||||
|
),
|
||||||
|
/// The signed embedded elliptic curve keys for an Ethereum validator.
|
||||||
|
Ethereum(
|
||||||
|
<<Embedwards25519 as Ciphersuite>::G as GroupEncoding>::Repr,
|
||||||
|
<<Secq256k1 as Ciphersuite>::G as GroupEncoding>::Repr,
|
||||||
|
[u8; 64],
|
||||||
|
[u8; 65],
|
||||||
|
),
|
||||||
|
/// The signed embedded elliptic curve key for a Monero validator.
|
||||||
|
Monero(<<Embedwards25519 as Ciphersuite>::G as GroupEncoding>::Repr, [u8; 64]),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignedEmbeddedEllipticCurveKeys {
|
||||||
|
/// The network these keys are for.
|
||||||
|
pub fn network(&self) -> ExternalNetworkId {
|
||||||
|
match self {
|
||||||
|
Self::Bitcoin(_, _, _, _) => ExternalNetworkId::Bitcoin,
|
||||||
|
Self::Ethereum(_, _, _, _) => ExternalNetworkId::Ethereum,
|
||||||
|
Self::Monero(_, _) => ExternalNetworkId::Monero,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify these key(s)' signature(s), returning the key(s) if valid.
|
||||||
|
pub fn verify(self, validator: Public) -> Option<EmbeddedEllipticCurveKeys> {
|
||||||
|
// Sample a unified challenge
|
||||||
|
let transcript = match &self {
|
||||||
|
Self::Bitcoin(e, s, e_sig, s_sig) => [
|
||||||
|
[ExternalNetworkId::Bitcoin as u8].as_slice(),
|
||||||
|
&validator.0,
|
||||||
|
e,
|
||||||
|
s,
|
||||||
|
&e_sig[.. 32],
|
||||||
|
&s_sig[.. 33],
|
||||||
|
]
|
||||||
|
.concat(),
|
||||||
|
Self::Ethereum(e, s, e_sig, s_sig) => [
|
||||||
|
[ExternalNetworkId::Ethereum as u8].as_slice(),
|
||||||
|
&validator.0,
|
||||||
|
e,
|
||||||
|
s,
|
||||||
|
&e_sig[.. 32],
|
||||||
|
&s_sig[.. 33],
|
||||||
|
]
|
||||||
|
.concat(),
|
||||||
|
Self::Monero(e, e_sig) => {
|
||||||
|
[[ExternalNetworkId::Monero as u8].as_slice(), &validator.0, e, &e_sig[.. 32]].concat()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let challenge = sp_core::hashing::blake2_512(&transcript);
|
||||||
|
|
||||||
|
// Verify the Schnorr signatures
|
||||||
|
match &self {
|
||||||
|
Self::Bitcoin(e, _, e_sig, _) | Self::Ethereum(e, _, e_sig, _) | Self::Monero(e, e_sig) => {
|
||||||
|
let sig = SchnorrSignature::<Embedwards25519>::read(&mut e_sig.as_slice()).ok()?;
|
||||||
|
if !sig.verify(
|
||||||
|
Embedwards25519::read_G(&mut e.as_slice()).ok()?,
|
||||||
|
<<Embedwards25519 as Ciphersuite>::F as FromUniformBytes<_>>::from_uniform_bytes(
|
||||||
|
&challenge,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
None?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match &self {
|
||||||
|
Self::Bitcoin(_, s, _, s_sig) | Self::Ethereum(_, s, _, s_sig) => {
|
||||||
|
let sig = SchnorrSignature::<Secq256k1>::read(&mut s_sig.as_slice()).ok()?;
|
||||||
|
if !sig.verify(
|
||||||
|
Secq256k1::read_G(&mut s.as_slice()).ok()?,
|
||||||
|
<<Secq256k1 as Ciphersuite>::F as FromUniformBytes<_>>::from_uniform_bytes(&challenge),
|
||||||
|
) {
|
||||||
|
None?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Monero(_, _) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the keys
|
||||||
|
Some(match self {
|
||||||
|
Self::Bitcoin(e, s, _, _) => EmbeddedEllipticCurveKeys::Bitcoin(e, s),
|
||||||
|
Self::Ethereum(e, s, _, _) => EmbeddedEllipticCurveKeys::Ethereum(e, s),
|
||||||
|
Self::Monero(e, _) => EmbeddedEllipticCurveKeys::Monero(e),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "non_canonical_scale_derivations")]
|
||||||
|
impl scale::Encode for SignedEmbeddedEllipticCurveKeys {
|
||||||
|
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
|
||||||
|
match self {
|
||||||
|
SignedEmbeddedEllipticCurveKeys::Bitcoin(e, s, e_sig, s_sig) |
|
||||||
|
SignedEmbeddedEllipticCurveKeys::Ethereum(e, s, e_sig, s_sig) => {
|
||||||
|
let mut res = [0; 195];
|
||||||
|
res[0] = self.network() as u8;
|
||||||
|
res[1 .. 33].copy_from_slice(e);
|
||||||
|
res[33 .. 66].copy_from_slice(s);
|
||||||
|
res[66 .. 130].copy_from_slice(e_sig);
|
||||||
|
res[130 ..].copy_from_slice(s_sig);
|
||||||
|
f(&res)
|
||||||
|
}
|
||||||
|
SignedEmbeddedEllipticCurveKeys::Monero(e, e_sig) => {
|
||||||
|
let mut res = [0; 97];
|
||||||
|
res[0] = self.network() as u8;
|
||||||
|
res[1 .. 33].copy_from_slice(e);
|
||||||
|
res[33 ..].copy_from_slice(e_sig);
|
||||||
|
f(&res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "non_canonical_scale_derivations")]
|
||||||
|
impl scale::EncodeLike<SignedEmbeddedEllipticCurveKeys> for SignedEmbeddedEllipticCurveKeys {}
|
||||||
|
#[cfg(feature = "non_canonical_scale_derivations")]
|
||||||
|
impl scale::Decode for SignedEmbeddedEllipticCurveKeys {
|
||||||
|
fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
|
||||||
|
let embedded_elliptic_curve_keys = EmbeddedEllipticCurveKeys::decode(input)?;
|
||||||
|
let embedwards25519_signature = <[u8; 64]>::decode(&mut *input)?;
|
||||||
|
Ok(match embedded_elliptic_curve_keys {
|
||||||
|
EmbeddedEllipticCurveKeys::Bitcoin(e, s) => {
|
||||||
|
let secq256k1_signature = <[u8; 65]>::decode(&mut *input)?;
|
||||||
|
SignedEmbeddedEllipticCurveKeys::Bitcoin(
|
||||||
|
e,
|
||||||
|
s,
|
||||||
|
embedwards25519_signature,
|
||||||
|
secq256k1_signature,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
EmbeddedEllipticCurveKeys::Ethereum(e, s) => {
|
||||||
|
let secq256k1_signature = <[u8; 65]>::decode(&mut *input)?;
|
||||||
|
SignedEmbeddedEllipticCurveKeys::Ethereum(
|
||||||
|
e,
|
||||||
|
s,
|
||||||
|
embedwards25519_signature,
|
||||||
|
secq256k1_signature,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
EmbeddedEllipticCurveKeys::Monero(e) => {
|
||||||
|
SignedEmbeddedEllipticCurveKeys::Monero(e, embedwards25519_signature)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "non_canonical_scale_derivations")]
|
||||||
|
impl scale::DecodeWithMemTracking for SignedEmbeddedEllipticCurveKeys {}
|
||||||
|
|
||||||
/// The key pair for a validator set.
|
/// The key pair for a validator set.
|
||||||
///
|
///
|
||||||
|
|||||||
Reference in New Issue
Block a user