From ec6e69f6fd09e0de99bec18611f3dbc923465dce Mon Sep 17 00:00:00 2001 From: 0xZensh Date: Fri, 22 Dec 2023 22:28:16 +0800 Subject: [PATCH] feat: improve ns-protocol crate --- crates/ns-fetcher/src/fetcher.rs | 2 +- crates/ns-fetcher/src/indexer.rs | 2 +- crates/ns-indexer/src/api/inscription.rs | 2 +- crates/ns-indexer/src/api/name.rs | 2 +- crates/ns-indexer/src/api/service.rs | 2 +- crates/ns-indexer/src/db/model_inscription.rs | 30 +-- crates/ns-indexer/src/db/model_name_state.rs | 8 +- .../src/db/model_service_protocol.rs | 12 +- .../ns-indexer/src/db/model_service_state.rs | 12 +- crates/ns-indexer/src/envelope.rs | 11 +- crates/ns-indexer/src/indexer.rs | 2 +- crates/ns-inscriber/src/bin/main.rs | 14 +- crates/ns-inscriber/src/inscriber.rs | 10 +- crates/ns-protocol/Cargo.toml | 2 +- crates/ns-protocol/src/ed25519.rs | 1 + crates/ns-protocol/src/lib.rs | 3 +- crates/ns-protocol/src/ns.rs | 181 +++++++++++------- crates/ns-protocol/src/{index.rs => state.rs} | 18 +- 18 files changed, 190 insertions(+), 124 deletions(-) create mode 100644 crates/ns-protocol/src/ed25519.rs rename crates/ns-protocol/src/{index.rs => state.rs} (92%) diff --git a/crates/ns-fetcher/src/fetcher.rs b/crates/ns-fetcher/src/fetcher.rs index e2692f0..fb8a7b3 100644 --- a/crates/ns-fetcher/src/fetcher.rs +++ b/crates/ns-fetcher/src/fetcher.rs @@ -2,7 +2,7 @@ use async_stream::try_stream; use bloomfilter::Bloom; use futures_core::stream::Stream; -use ns_protocol::index::{Inscription, NameState, ServiceState}; +use ns_protocol::state::{Inscription, NameState, ServiceState}; use crate::indexer::Client; diff --git a/crates/ns-fetcher/src/indexer.rs b/crates/ns-fetcher/src/indexer.rs index de8dad4..7e2c3d6 100644 --- a/crates/ns-fetcher/src/indexer.rs +++ b/crates/ns-fetcher/src/indexer.rs @@ -4,8 +4,8 @@ use serde::{de::DeserializeOwned, Deserialize}; use tokio::time::{sleep, Duration}; use ns_protocol::{ - index::{Inscription, NameState, ServiceState}, ns::Value, + state::{Inscription, NameState, ServiceState}, }; static APP_USER_AGENT: &str = concat!( diff --git a/crates/ns-indexer/src/api/inscription.rs b/crates/ns-indexer/src/api/inscription.rs index b736c6d..f64be1b 100644 --- a/crates/ns-indexer/src/api/inscription.rs +++ b/crates/ns-indexer/src/api/inscription.rs @@ -10,7 +10,7 @@ use axum_web::{ erring::{HTTPError, SuccessResponse}, object::PackObject, }; -use ns_protocol::index::{Inscription, InvalidInscription}; +use ns_protocol::state::{Inscription, InvalidInscription}; use crate::api::{IndexerAPI, QueryHeight, QueryName, QueryNamePagination}; use crate::db; diff --git a/crates/ns-indexer/src/api/name.rs b/crates/ns-indexer/src/api/name.rs index dd5450b..0634ebe 100644 --- a/crates/ns-indexer/src/api/name.rs +++ b/crates/ns-indexer/src/api/name.rs @@ -10,7 +10,7 @@ use axum_web::{ erring::{HTTPError, SuccessResponse}, object::PackObject, }; -use ns_protocol::index::NameState; +use ns_protocol::state::NameState; use crate::api::{IndexerAPI, QueryName, QueryPubkey}; use crate::db; diff --git a/crates/ns-indexer/src/api/service.rs b/crates/ns-indexer/src/api/service.rs index 6878c6d..5cc2af4 100644 --- a/crates/ns-indexer/src/api/service.rs +++ b/crates/ns-indexer/src/api/service.rs @@ -10,7 +10,7 @@ use axum_web::{ erring::{HTTPError, SuccessResponse}, object::PackObject, }; -use ns_protocol::index::ServiceState; +use ns_protocol::state::ServiceState; use crate::api::{IndexerAPI, QueryName}; use crate::db; diff --git a/crates/ns-indexer/src/db/model_inscription.rs b/crates/ns-indexer/src/db/model_inscription.rs index 2f653a6..945a060 100644 --- a/crates/ns-indexer/src/db/model_inscription.rs +++ b/crates/ns-indexer/src/db/model_inscription.rs @@ -2,7 +2,7 @@ use axum_web::erring::HTTPError; use scylla_orm::{ColumnsMap, CqlValue, ToCqlVal}; use scylla_orm_macros::CqlOrm; -use ns_protocol::index; +use ns_protocol::state; use crate::db::{self, scylladb, scylladb::filter_single_row_err}; @@ -142,8 +142,8 @@ impl Inscription { } } - pub fn from_index(value: &index::Inscription) -> anyhow::Result { - let data = index::to_bytes(&value.data)?; + pub fn from_index(value: &state::Inscription) -> anyhow::Result { + let data = state::to_bytes(&value.data)?; Ok(Self { name: value.name.clone(), sequence: value.sequence as i64, @@ -161,9 +161,9 @@ impl Inscription { }) } - pub fn to_index(&self) -> anyhow::Result { - let data = index::from_bytes(&self.data)?; - Ok(index::Inscription { + pub fn to_index(&self) -> anyhow::Result { + let data = state::from_bytes(&self.data)?; + Ok(state::Inscription { name: self.name.clone(), sequence: self.sequence as u64, height: self.height as u64, @@ -252,10 +252,10 @@ impl Inscription { // save inscriptions and states in a block to db pub async fn save_checkpoint( db: &scylladb::ScyllaDB, - name_states: &Vec, - service_states: &Vec, - protocol_states: &Vec, - inscriptions: &Vec, + name_states: &Vec, + service_states: &Vec, + protocol_states: &Vec, + inscriptions: &Vec, ) -> anyhow::Result<()> { let mut statements: Vec<&str> = Vec::with_capacity(1024); let mut values: Vec> = Vec::with_capacity(1024); @@ -486,8 +486,8 @@ impl Inscription { } impl InvalidInscription { - pub fn from_index(value: &index::InvalidInscription) -> anyhow::Result { - let data = index::to_bytes(&value.data)?; + pub fn from_index(value: &state::InvalidInscription) -> anyhow::Result { + let data = state::to_bytes(&value.data)?; Ok(Self { name: value.name.clone(), block_height: value.block_height as i64, @@ -498,9 +498,9 @@ impl InvalidInscription { }) } - pub fn to_index(&self) -> anyhow::Result { - let data = index::from_bytes(&self.data)?; - Ok(index::InvalidInscription { + pub fn to_index(&self) -> anyhow::Result { + let data = state::from_bytes(&self.data)?; + Ok(state::InvalidInscription { name: self.name.clone(), block_height: self.block_height as u64, hash: self.hash.clone(), diff --git a/crates/ns-indexer/src/db/model_name_state.rs b/crates/ns-indexer/src/db/model_name_state.rs index 19ffc64..2cf3a1f 100644 --- a/crates/ns-indexer/src/db/model_name_state.rs +++ b/crates/ns-indexer/src/db/model_name_state.rs @@ -3,7 +3,7 @@ use scylla_orm::{ColumnsMap, CqlValue, ToCqlVal}; use scylla_orm_macros::CqlOrm; use std::collections::{BTreeMap, HashSet}; -use ns_protocol::index; +use ns_protocol::state; use crate::db::scylladb; @@ -45,7 +45,7 @@ impl NameState { } } - pub fn from_index(value: &index::NameState) -> anyhow::Result { + pub fn from_index(value: &state::NameState) -> anyhow::Result { Ok(Self { name: value.name.clone(), sequence: value.sequence as i64, @@ -59,8 +59,8 @@ impl NameState { }) } - pub fn to_index(&self) -> anyhow::Result { - Ok(index::NameState { + pub fn to_index(&self) -> anyhow::Result { + Ok(state::NameState { name: self.name.clone(), sequence: self.sequence as u64, block_height: self.block_height as u64, diff --git a/crates/ns-indexer/src/db/model_service_protocol.rs b/crates/ns-indexer/src/db/model_service_protocol.rs index a6b739c..0368cea 100644 --- a/crates/ns-indexer/src/db/model_service_protocol.rs +++ b/crates/ns-indexer/src/db/model_service_protocol.rs @@ -2,7 +2,7 @@ use axum_web::erring::HTTPError; use scylla_orm::{ColumnsMap, ToCqlVal}; use scylla_orm_macros::CqlOrm; -use ns_protocol::index; +use ns_protocol::state; use crate::db::scylladb; @@ -26,8 +26,8 @@ impl ServiceProtocol { } } - pub fn from_index(value: &index::ServiceProtocol) -> anyhow::Result { - let protocol = index::to_bytes(&value.protocol)?; + pub fn from_index(value: &state::ServiceProtocol) -> anyhow::Result { + let protocol = state::to_bytes(&value.protocol)?; Ok(Self { code: value.code as i64, version: value.version as i32, @@ -38,9 +38,9 @@ impl ServiceProtocol { }) } - pub fn to_index(&self) -> anyhow::Result { - let protocol = index::from_bytes(&self.protocol)?; - Ok(index::ServiceProtocol { + pub fn to_index(&self) -> anyhow::Result { + let protocol = state::from_bytes(&self.protocol)?; + Ok(state::ServiceProtocol { code: self.code as u64, version: self.version as u16, protocol, diff --git a/crates/ns-indexer/src/db/model_service_state.rs b/crates/ns-indexer/src/db/model_service_state.rs index e6c5f36..360a5c2 100644 --- a/crates/ns-indexer/src/db/model_service_state.rs +++ b/crates/ns-indexer/src/db/model_service_state.rs @@ -2,7 +2,7 @@ use axum_web::erring::HTTPError; use scylla_orm::{ColumnsMap, ToCqlVal}; use scylla_orm_macros::CqlOrm; -use ns_protocol::index; +use ns_protocol::state; use crate::db::scylladb; @@ -25,8 +25,8 @@ impl ServiceState { } } - pub fn from_index(value: &index::ServiceState) -> anyhow::Result { - let data = index::to_bytes(&value.data)?; + pub fn from_index(value: &state::ServiceState) -> anyhow::Result { + let data = state::to_bytes(&value.data)?; Ok(Self { name: value.name.clone(), code: value.code as i64, @@ -36,9 +36,9 @@ impl ServiceState { }) } - pub fn to_index(&self) -> anyhow::Result { - let data = index::from_bytes(&self.data)?; - Ok(index::ServiceState { + pub fn to_index(&self) -> anyhow::Result { + let data = state::from_bytes(&self.data)?; + Ok(state::ServiceState { name: self.name.clone(), code: self.code as u64, sequence: self.sequence as u64, diff --git a/crates/ns-indexer/src/envelope.rs b/crates/ns-indexer/src/envelope.rs index 1730bb0..8a065e4 100644 --- a/crates/ns-indexer/src/envelope.rs +++ b/crates/ns-indexer/src/envelope.rs @@ -78,7 +78,10 @@ mod tests { use bitcoin::blockdata::script::{Builder, PushBytesBuf}; use ciborium::Value; use hex_literal::hex; - use ns_protocol::ns::{Operation, PublicKeyParams, Service, ThresholdLevel}; + use ns_protocol::{ + ed25519, + ns::{Operation, PublicKeyParams, Service, ThresholdLevel}, + }; #[test] fn names_from_witness() { @@ -89,6 +92,8 @@ mod tests { threshold: Some(1), kind: None, }; + let signer = ed25519::SigningKey::try_from(&secret_key).unwrap(); + let signers = vec![signer]; let mut name1 = Name { name: "a".to_string(), @@ -104,7 +109,7 @@ mod tests { signatures: vec![], }; name1 - .sign(¶ms, ThresholdLevel::Default, &[secret_key.to_vec()]) + .sign(¶ms, ThresholdLevel::Default, &signers) .unwrap(); assert!(name1.validate().is_ok()); @@ -122,7 +127,7 @@ mod tests { signatures: vec![], }; name2 - .sign(¶ms, ThresholdLevel::Default, &[secret_key.to_vec()]) + .sign(¶ms, ThresholdLevel::Default, &signers) .unwrap(); assert!(name2.validate().is_ok()); diff --git a/crates/ns-indexer/src/indexer.rs b/crates/ns-indexer/src/indexer.rs index c6a1992..722160d 100644 --- a/crates/ns-indexer/src/indexer.rs +++ b/crates/ns-indexer/src/indexer.rs @@ -6,8 +6,8 @@ use std::{ use tokio::sync::RwLock; use ns_protocol::{ - index::{hash_sha3, Inscription, InvalidInscription, NameState, ServiceProtocol, ServiceState}, ns::{Name, PublicKeyParams, ThresholdLevel}, + state::{hash_sha3, Inscription, InvalidInscription, NameState, ServiceProtocol, ServiceState}, }; use crate::db::{ diff --git a/crates/ns-inscriber/src/bin/main.rs b/crates/ns-inscriber/src/bin/main.rs index 14d8457..ce68f9b 100644 --- a/crates/ns-inscriber/src/bin/main.rs +++ b/crates/ns-inscriber/src/bin/main.rs @@ -449,6 +449,7 @@ async fn main() -> anyhow::Result<()> { kind: None, }; + let signers = vec![signing_key]; let mut ns: Vec = Vec::with_capacity(names.len()); for name in &names { let mut n = Name { @@ -464,11 +465,7 @@ async fn main() -> anyhow::Result<()> { }, signatures: vec![], }; - n.sign( - ¶ms, - ThresholdLevel::All, - &[signing_key.as_bytes().to_vec()], - )?; + n.sign(¶ms, ThresholdLevel::All, &signers)?; n.validate()?; ns.push(n); } @@ -521,6 +518,7 @@ async fn main() -> anyhow::Result<()> { kind: None, }; + let signers = vec![signing_key]; let mut ns: Vec = Vec::with_capacity(names.len()); for name in &names { let mut n = Name { @@ -536,11 +534,7 @@ async fn main() -> anyhow::Result<()> { }, signatures: vec![], }; - n.sign( - ¶ms, - ThresholdLevel::All, - &[signing_key.as_bytes().to_vec()], - )?; + n.sign(¶ms, ThresholdLevel::All, &signers)?; n.validate()?; ns.push(n); } diff --git a/crates/ns-inscriber/src/inscriber.rs b/crates/ns-inscriber/src/inscriber.rs index 88f24ea..125725f 100644 --- a/crates/ns-inscriber/src/inscriber.rs +++ b/crates/ns-inscriber/src/inscriber.rs @@ -563,7 +563,10 @@ mod tests { use serde_json::to_value; use ns_indexer::envelope::Envelope; - use ns_protocol::ns::{Operation, PublicKeyParams, Service, ThresholdLevel, Value}; + use ns_protocol::{ + ed25519, + ns::{Operation, PublicKeyParams, Service, ThresholdLevel, Value}, + }; fn get_name(name: &str) -> Name { let secret_key = hex!("7ef3811aabb916dc2f646ef1a371b90adec91bc07992cd4d44c156c42fc1b300"); @@ -574,6 +577,9 @@ mod tests { kind: None, }; + let signer = ed25519::SigningKey::try_from(&secret_key).unwrap(); + let signers = vec![signer]; + let mut name = Name { name: name.to_string(), sequence: 0, @@ -587,7 +593,7 @@ mod tests { }, signatures: vec![], }; - name.sign(¶ms, ThresholdLevel::Default, &[secret_key.to_vec()]) + name.sign(¶ms, ThresholdLevel::Default, &signers) .unwrap(); assert!(name.validate().is_ok()); name diff --git a/crates/ns-protocol/Cargo.toml b/crates/ns-protocol/Cargo.toml index de6fb0c..8667d71 100644 --- a/crates/ns-protocol/Cargo.toml +++ b/crates/ns-protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ns-protocol" -version = "0.3.0" +version = "0.4.0" edition = "2021" rust-version = "1.64" description = "Name & Service Protocol in Rust" diff --git a/crates/ns-protocol/src/ed25519.rs b/crates/ns-protocol/src/ed25519.rs new file mode 100644 index 0000000..b819178 --- /dev/null +++ b/crates/ns-protocol/src/ed25519.rs @@ -0,0 +1 @@ +pub use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; diff --git a/crates/ns-protocol/src/lib.rs b/crates/ns-protocol/src/lib.rs index aca8eab..68b7d69 100644 --- a/crates/ns-protocol/src/lib.rs +++ b/crates/ns-protocol/src/lib.rs @@ -1,2 +1,3 @@ -pub mod index; +pub mod ed25519; pub mod ns; +pub mod state; diff --git a/crates/ns-protocol/src/ns.rs b/crates/ns-protocol/src/ns.rs index 53b2170..e2dcdbd 100644 --- a/crates/ns-protocol/src/ns.rs +++ b/crates/ns-protocol/src/ns.rs @@ -1,8 +1,4 @@ use ciborium_io::{Read, Write}; -use ed25519_dalek::{ - Signature as Ed25519Signature, Signer as Ed25519Signer, SigningKey as Ed25519SigningKey, - VerifyingKey as Ed25519VerifyingKey, -}; use finl_unicode::categories::CharacterCategories; use serde::{de, ser, Deserialize, Serialize}; use std::{convert::From, fmt::Debug}; @@ -12,6 +8,8 @@ pub use ciborium::{ value::{Error, Value}, }; +use crate::ed25519::{self, Signer}; + pub type NSTag = tag::Required; pub type NSTagRef<'a> = tag::Required<&'a Name, 53>; pub const MAX_NAME_BYTES: usize = 520; @@ -50,6 +48,9 @@ impl core::default::Default for Operation { #[derive(Clone, PartialEq, Eq, Debug, Default)] pub struct Signature(pub Vec); +// PublicKeyParams is Ed25519 Multisignatures with threshold, +// every public key can be FROST (Flexible Round-Optimised Schnorr Threshold signatures) +// see: https://github.com/ZcashFoundation/frost #[derive(Clone, PartialEq, Eq, Debug, Default)] pub struct PublicKeyParams { pub public_keys: Vec>, @@ -70,13 +71,13 @@ impl PublicKeyParams { pub fn validate(&self) -> Result<(), Error> { if self.public_keys.is_empty() { return Err(Error::Custom( - "expected at least one public key".to_string(), + "PublicKeyParams: expected at least one public key".to_string(), )); } for pk in self.public_keys.iter() { if pk.len() != 32 { return Err(Error::Custom(format!( - "expected public key length 32, got {:?}", + "PublicKeyParams: expected key length is 32, got {:?}", pk.len() ))); } @@ -84,12 +85,12 @@ impl PublicKeyParams { if let Some(threshold) = self.threshold { if threshold == 0 { return Err(Error::Custom( - "threshold must be greater than 0".to_string(), + "PublicKeyParams: threshold must be greater than 0".to_string(), )); } if threshold > self.public_keys.len() as u8 { return Err(Error::Custom(format!( - "threshold {} is greater than number of public keys {}", + "PublicKeyParams: threshold {} is greater than number of public keys {}", threshold, self.public_keys.len() ))); @@ -98,7 +99,7 @@ impl PublicKeyParams { if let Some(kind) = self.kind { if kind != 0 { return Err(Error::Custom(format!( - "unsupported public key kind {}", + "PublicKeyParams: unsupported public key kind {}", kind ))); } @@ -107,8 +108,7 @@ impl PublicKeyParams { public_keys.dedup(); if public_keys.len() != self.public_keys.len() { return Err(Error::Custom(format!( - "duplicate public_keys {:?}", - self.public_keys + "PublicKeyParams: duplicate public_keys", ))); } @@ -133,6 +133,7 @@ impl PublicKeyParams { } } +// name should be valid utf-8 string, not empty, not longer than 64 bytes, and not contain any of the following characters: uppercase letters, punctuations, separators, marks, symbols, and other control characters, format characters, surrogates, unassigned characters and private use characters. // https://docs.rs/finl_unicode/latest/finl_unicode/categories/trait.CharacterCategories.html pub fn valid_name(name: &str) -> bool { let mut size = 0; @@ -161,8 +162,8 @@ impl Name { where R::Error: core::fmt::Debug, { - let value: NSTag = - from_reader(r).map_err(|err| Error::Custom(format!("decode_from: {:?}", err)))?; + let value: NSTag = from_reader(r) + .map_err(|err| Error::Custom(format!("Name: decode_from error, {:?}", err)))?; Ok(value.0) } @@ -171,20 +172,21 @@ impl Name { W::Error: core::fmt::Debug, { let v: NSTagRef = tag::Required(self); - into_writer(&v, w).map_err(|err| Error::Custom(format!("encode_to failed: {:?}", err)))?; + into_writer(&v, w) + .map_err(|err| Error::Custom(format!("Name: encode_to error, {:?}", err)))?; Ok(()) } pub fn from_bytes(buf: &[u8]) -> Result { if !buf.starts_with(&NS_PREFIX) { - return Err(Error::Custom("invalid name bytes".to_string())); + return Err(Error::Custom("Name: invalid bytes".to_string())); } let name = Self::decode_from(buf)?; let data = name.to_bytes()?; if !buf.eq(&data) { return Err(Error::Custom( - "from_bytes: data not consumed entirely".to_string(), + "Name: data not consumed entirely".to_string(), )); } Ok(name) @@ -212,12 +214,12 @@ impl Name { // validate the name is well-formed pub fn validate(&self) -> Result<(), Error> { if !valid_name(&self.name) { - return Err(Error::Custom(format!("invalid name {}", self.name))); + return Err(Error::Custom(format!("Name: invalid name {}", self.name))); } if self.sequence > i64::MAX as u64 { return Err(Error::Custom(format!( - "invalid sequence {}, expected less than {}", + "Name: invalid sequence {}, expected less than {}", self.sequence, i64::MAX ))); @@ -225,7 +227,7 @@ impl Name { if self.payload.code > i64::MAX as u64 { return Err(Error::Custom(format!( - "invalid payload code {}, expected less than {}", + "Name: invalid payload code {}, expected less than {}", self.payload.code, i64::MAX ))); @@ -233,21 +235,24 @@ impl Name { if let Some(approver) = &self.payload.approver { if !valid_name(approver) { - return Err(Error::Custom(format!("invalid approver {}", approver))); + return Err(Error::Custom(format!( + "Name: invalid approver {}", + approver + ))); } } if self.payload.operations.is_empty() { - return Err(Error::Custom("missing operations".to_string())); + return Err(Error::Custom("Name: missing operations".to_string())); } if self.signatures.is_empty() { - return Err(Error::Custom("missing signatures".to_string())); + return Err(Error::Custom("Name: missing signatures".to_string())); } for sig in self.signatures.iter() { if sig.0.len() != 64 { return Err(Error::Custom(format!( - "expected signature length 64, got {:?}", + "Name: expected signature length is 64, got {:?}", sig.0.len() ))); } @@ -256,10 +261,7 @@ impl Name { let mut signatures = self.signatures.clone(); signatures.dedup(); if signatures.len() != self.signatures.len() { - return Err(Error::Custom(format!( - "duplicate signatures {:?}", - self.signatures - ))); + return Err(Error::Custom(format!("Name: duplicate signatures",))); } Ok(()) } @@ -269,7 +271,7 @@ impl Name { let threshold = params.verifying_threshold(level); if threshold == 0 { return Err(Error::Custom( - "threshold must be greater than 0".to_string(), + "Name: threshold must be greater than 0".to_string(), )); } @@ -277,10 +279,10 @@ impl Name { let mut keys = params.public_keys.iter(); let mut count = 0; for sig in self.signatures.iter() { - let sig = Ed25519Signature::from_slice(&sig.0) + let sig = ed25519::Signature::from_slice(&sig.0) .map_err(|err| Error::Custom(err.to_string()))?; for key in keys.by_ref() { - let verifying_key = Ed25519VerifyingKey::try_from(key.as_slice()) + let verifying_key = ed25519::VerifyingKey::try_from(key.as_slice()) .map_err(|err| Error::Custom(err.to_string()))?; if verifying_key.verify_strict(&data, &sig).is_ok() { count += 1; @@ -294,7 +296,7 @@ impl Name { } Err(Error::Custom(format!( - "verify failed, expected {} signatures, got {}", + "Name: verify failed, expected {} signatures, got {}", threshold, count ))) } @@ -303,26 +305,20 @@ impl Name { &mut self, params: &PublicKeyParams, level: ThresholdLevel, - secret_keys: &[Vec], + signers: &[ed25519::SigningKey], ) -> Result<(), Error> { let threshold = params.verifying_threshold(level); if threshold == 0 { return Err(Error::Custom( - "threshold must be greater than 0".to_string(), + "Name: threshold must be greater than 0".to_string(), )); } let data = self.to_sign_bytes()?; - let signing_keys = secret_keys - .iter() - .map(|key| Ed25519SigningKey::try_from(key.as_slice())) - .collect::, ed25519_dalek::ed25519::Error>>() - .map_err(|err| Error::Custom(err.to_string()))?; - self.signatures = Vec::with_capacity(threshold as usize); // siging in order of public keys for pk in params.public_keys.iter() { - if let Some(signer) = signing_keys + if let Some(signer) = signers .iter() .find(|sk| sk.verifying_key().as_bytes().as_slice() == pk) { @@ -330,10 +326,18 @@ impl Name { self.signatures.push(sig); } } + if self.signatures.len() != threshold as usize { + return Err(Error::Custom(format!( + "Name: expected {} signatures, got {}", + threshold, + self.signatures.len() + ))); + } + Ok(()) } - pub fn sign_with(&mut self, signer: &Ed25519SigningKey) -> Result<(), Error> { + pub fn sign_with(&mut self, signer: &ed25519::SigningKey) -> Result<(), Error> { let data = self.to_sign_bytes()?; let sig = Signature(signer.sign(&data).to_bytes().to_vec()); self.signatures.push(sig); @@ -407,7 +411,7 @@ impl TryFrom<&Value> for Signature { Value::Bytes(bytes) => { if bytes.len() != 64 { Err(Error::Custom(format!( - "expected value length 64, got {:?}", + "Signature: expected value length is 64, got {:?}", bytes.len() ))) } else { @@ -417,7 +421,7 @@ impl TryFrom<&Value> for Signature { } } _ => Err(Error::Custom(format!( - "expected bytes, got {}", + "Signature: expected bytes, got {}", kind_of_value(value) ))), } @@ -431,7 +435,7 @@ impl TryFrom<&Value> for Operation { Value::Array(arr) => { if arr.len() != 2 { return Err(Error::Custom(format!( - "expected array of length 2, got {:?}", + "Operation: expected array of length is 2, got {:?}", arr.len() ))); } @@ -441,17 +445,19 @@ impl TryFrom<&Value> for Operation { .as_integer() .ok_or_else(|| { Error::Custom(format!( - "expected integer, got {}", + "Operation: expected integer, got {}", kind_of_value(&arr[0]) )) })? .try_into() - .map_err(|err| Error::Custom(format!("expected u32, error: {:?}", err)))?, + .map_err(|err| { + Error::Custom(format!("Operation: expected u32, error, {:?}", err)) + })?, params: arr[1].clone(), }) } _ => Err(Error::Custom(format!( - "expected array, got {}", + "Operation: expected array, got {}", kind_of_value(value) ))), } @@ -462,7 +468,10 @@ impl TryFrom<&Value> for Service { type Error = Error; fn try_from(value: &Value) -> Result { let arr = value.as_array().ok_or_else(|| { - Error::Custom(format!("expected array, got {}", kind_of_value(value))) + Error::Custom(format!( + "Service: expected array, got {}", + kind_of_value(value) + )) })?; match arr.len() { v if v == 2 || v == 3 => { @@ -471,16 +480,21 @@ impl TryFrom<&Value> for Service { .as_integer() .ok_or_else(|| { Error::Custom(format!( - "expected integer, got {}", + "Service: expected integer, got {}", kind_of_value(&arr[0]) )) })? .try_into() - .map_err(|err| Error::Custom(format!("expected u32, error: {:?}", err)))?, + .map_err(|err| { + Error::Custom(format!("Service: expected u32, error: {:?}", err)) + })?, operations: arr[1] .as_array() .ok_or_else(|| { - Error::Custom(format!("expected array, got {}", kind_of_value(&arr[1]))) + Error::Custom(format!( + "Service: expected array, got {}", + kind_of_value(&arr[1]) + )) })? .iter() .map(Operation::try_from) @@ -489,14 +503,17 @@ impl TryFrom<&Value> for Service { }; if v == 3 { let approver = arr[2].as_text().ok_or_else(|| { - Error::Custom(format!("expected text, got {}", kind_of_value(&arr[2]))) + Error::Custom(format!( + "Service: expected text, got {}", + kind_of_value(&arr[2]) + )) })?; srv.approver = Some(approver.to_string()); } Ok(srv) } v => Err(Error::Custom(format!( - "expected array of length 2 or 3, got {:?}", + "Service: expected array of length 2 or 3, got {}", v ))), } @@ -507,7 +524,10 @@ impl TryFrom<&Value> for PublicKeyParams { type Error = Error; fn try_from(value: &Value) -> Result { let arr = value.as_array().ok_or_else(|| { - Error::Custom(format!("expected array, got {}", kind_of_value(value))) + Error::Custom(format!( + "PublicKeyParams: expected array, got {}", + kind_of_value(value) + )) })?; match arr.len() { v if (1..=3).contains(&v) => { @@ -515,12 +535,18 @@ impl TryFrom<&Value> for PublicKeyParams { public_keys: arr[0] .as_array() .ok_or_else(|| { - Error::Custom(format!("expected array, got {}", kind_of_value(&arr[0]))) + Error::Custom(format!( + "PublicKeyParams: expected array, got {}", + kind_of_value(&arr[0]) + )) })? .iter() .map(|pk| { pk.as_bytes().map(|v| v.to_owned()).ok_or_else(|| { - Error::Custom(format!("expected bytes, got {}", kind_of_value(pk))) + Error::Custom(format!( + "PublicKeyParams: expected bytes, got {}", + kind_of_value(pk) + )) }) }) .collect::>, Error>>()?, @@ -532,12 +558,14 @@ impl TryFrom<&Value> for PublicKeyParams { .as_integer() .ok_or_else(|| { Error::Custom(format!( - "expected integer, got {}", + "PublicKeyParams: expected integer, got {}", kind_of_value(&arr[1]) )) })? .try_into() - .map_err(|err| Error::Custom(format!("expected u8, error: {:?}", err)))?; + .map_err(|err| { + Error::Custom(format!("PublicKeyParams: expected u8, error: {:?}", err)) + })?; params.threshold = Some(threshold); } @@ -546,18 +574,20 @@ impl TryFrom<&Value> for PublicKeyParams { .as_integer() .ok_or_else(|| { Error::Custom(format!( - "expected integer, got {}", + "PublicKeyParams: expected integer, got {}", kind_of_value(&arr[2]) )) })? .try_into() - .map_err(|err| Error::Custom(format!("expected u8, error: {:?}", err)))?; + .map_err(|err| { + Error::Custom(format!("PublicKeyParams: expected u8, error: {:?}", err)) + })?; params.kind = Some(kind); } Ok(params) } v => Err(Error::Custom(format!( - "expected array of length [1, 3], got {:?}", + "PublicKeyParams: expected array of length [1, 3], got {}", v ))), } @@ -568,35 +598,49 @@ impl TryFrom<&Value> for Name { type Error = Error; fn try_from(value: &Value) -> Result { let arr = value.as_array().ok_or_else(|| { - Error::Custom(format!("expected array, got {}", kind_of_value(value))) + Error::Custom(format!( + "Name: expected array, got {}", + kind_of_value(value) + )) })?; match arr.len() { 4 => Ok(Name { name: arr[0] .as_text() .ok_or_else(|| { - Error::Custom(format!("expected text, got {}", kind_of_value(&arr[0]))) + Error::Custom(format!( + "Name: expected text, got {}", + kind_of_value(&arr[0]) + )) })? .to_string(), sequence: arr[1] .as_integer() .ok_or_else(|| { - Error::Custom(format!("expected integer, got {}", kind_of_value(&arr[1]))) + Error::Custom(format!( + "Name: expected integer, got {}", + kind_of_value(&arr[1]) + )) })? .try_into() - .map_err(|err| Error::Custom(format!("expected u64, error: {:?}", err)))?, + .map_err(|err| { + Error::Custom(format!("Name: expected u64, error: {:?}", err)) + })?, payload: Service::try_from(&arr[2])?, signatures: arr[3] .as_array() .ok_or_else(|| { - Error::Custom(format!("expected array, got {}", kind_of_value(&arr[3]))) + Error::Custom(format!( + "Name: expected array, got {}", + kind_of_value(&arr[3]) + )) })? .iter() .map(Signature::try_from) .collect::, Self::Error>>()?, }), _ => Err(Error::Custom(format!( - "expected array of length 4, got {:?}", + "Name: expected array of length 4, got {}", arr.len() ))), } @@ -764,6 +808,7 @@ mod tests { threshold: Some(1), kind: None, }; + let signer = ed25519::SigningKey::try_from(&secret_key).unwrap(); assert!(params.validate().is_ok()); let mut name = Name { @@ -780,7 +825,7 @@ mod tests { signatures: vec![], }; assert!(name.validate().is_err()); - name.sign(¶ms, ThresholdLevel::Default, &[secret_key.to_vec()]) + name.sign(¶ms, ThresholdLevel::Default, &[signer]) .unwrap(); assert!(name.validate().is_ok()); assert!(name.verify(¶ms, ThresholdLevel::Single).is_ok()); diff --git a/crates/ns-protocol/src/index.rs b/crates/ns-protocol/src/state.rs similarity index 92% rename from crates/ns-protocol/src/index.rs rename to crates/ns-protocol/src/state.rs index 90a4bd3..a179fcc 100644 --- a/crates/ns-protocol/src/index.rs +++ b/crates/ns-protocol/src/state.rs @@ -6,9 +6,9 @@ use std::{borrow::BorrowMut, fmt::Debug}; use crate::ns::{Error, Name, PublicKeyParams, Service, ThresholdLevel, Value}; // After the silence period exceeds 365 days, the name is invalid, the application validation signature should be invalid, and the original registrant can activate the name with any update. -pub const NAME_SILENT_SECONDS: u64 = 60 * 60 * 24 * 365; +pub const NAME_STALE_SECONDS: u64 = 60 * 60 * 24 * 365; // After the silence period exceeds 365 + 180 days, others are allowed to re-register the name, if no one registers, the original registrant can activate the name with any update -pub const NAME_EXPIRE_SECONDS: u64 = NAME_SILENT_SECONDS + 60 * 60 * 24 * 180; +pub const NAME_EXPIRE_SECONDS: u64 = NAME_STALE_SECONDS + 60 * 60 * 24 * 180; #[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq)] pub struct NameState { @@ -36,6 +36,10 @@ impl NameState { hash_sha3(self) } + pub fn is_stale(&self, block_time: u64) -> bool { + return self.block_time + NAME_STALE_SECONDS < block_time; + } + pub fn verify_the_next( &self, block_height: u64, @@ -70,6 +74,16 @@ impl NameState { // handle the `0` service code (Name service) let mut next_state = self.clone(); + if next.payload.operations.len() == 1 && next.payload.operations[0].subcode == 0 { + // This is the lightweight update operation + next.verify(&next_state.public_key_params(), ThresholdLevel::Default)?; + next_state.sequence = next.sequence; + next_state.block_height = block_height; + next_state.block_time = block_time; + next_state.next_public_keys = None; + return Ok(next_state); + } + for op in &next.payload.operations { let public_key_params = PublicKeyParams::try_from(&op.params)?; public_key_params.validate()?;