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

feat(common) Operation nonces #147

Merged
merged 11 commits into from
Oct 30, 2024
3 changes: 2 additions & 1 deletion Cargo.lock

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

107 changes: 107 additions & 0 deletions crates/common/src/digest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use anyhow::{anyhow, Result};
use bls12_381::Scalar;
use jmt::RootHash;
use serde::{Deserialize, Serialize};

use crate::hasher::Hasher;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Copy)]
pub struct Digest(pub [u8; 32]);

impl Digest {
pub fn hash(data: impl AsRef<[u8]>) -> Self {
let mut hasher = Hasher::new();
hasher.update(data.as_ref());
Self(hasher.finalize())
}

pub const fn zero() -> Self {
Self([0u8; 32])
}
}

// serializer and deserializer for rocksdb
// converts from bytearrays into digests
// padds it with zero if it is too small
impl<const N: usize> From<[u8; N]> for Digest {
fn from(value: [u8; N]) -> Self {
assert!(N <= 32, "Input array must not exceed 32 bytes");
let mut digest = [0u8; 32];
digest[..N].copy_from_slice(&value);
Self(digest)
}
}
jns-ps marked this conversation as resolved.
Show resolved Hide resolved

// implementing it for now to get things to compile, curve choice will be made later
impl TryFrom<Digest> for Scalar {
type Error = anyhow::Error;

fn try_from(value: Digest) -> Result<Scalar, Self::Error> {
let mut byte_array = [0u8; 32];
byte_array.copy_from_slice(value.as_ref());
byte_array.reverse();

let val =
[
u64::from_le_bytes(byte_array[0..8].try_into().map_err(|_| {
anyhow!(format!("slice to array: [0..8] for digest: {value:?}"))
})?),
u64::from_le_bytes(byte_array[8..16].try_into().map_err(|_| {
anyhow!(format!("slice to array: [8..16] for digest: {value:?}"))
})?),
u64::from_le_bytes(byte_array[16..24].try_into().map_err(|_| {
anyhow!(format!("slice to array: [16..24] for digest: {value:?}"))
})?),
u64::from_le_bytes(byte_array[24..32].try_into().map_err(|_| {
anyhow!(format!("slice to array: [24..32] for digest: {value:?}"))
})?),
];

Ok(Scalar::from_raw(val))
}
}

impl From<Digest> for RootHash {
fn from(val: Digest) -> RootHash {
RootHash::from(val.0)
}
}

impl From<RootHash> for Digest {
fn from(val: RootHash) -> Digest {
Digest(val.0)
}
}

impl AsRef<[u8]> for Digest {
fn as_ref(&self) -> &[u8] {
&self.0
}
}

impl std::fmt::Display for Digest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_hex())
}
}

impl Digest {
pub const fn new(bytes: [u8; 32]) -> Self {
Digest(bytes)
}

pub fn from_hex(hex_str: &str) -> Result<Self> {
let mut bytes = [0u8; 32];
hex::decode_to_slice(hex_str, &mut bytes)
.map_err(|e| anyhow!(format!("Invalid Format: {e}")))?;
Ok(Digest(bytes))
}

pub fn to_hex(&self) -> String {
hex::encode(self.0)
}

pub fn to_bytes(&self) -> [u8; 32] {
self.0
}
}
64 changes: 45 additions & 19 deletions crates/common/src/hashchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ use std::{
};

use crate::{
digest::Digest,
hasher::Hasher,
keys::VerifyingKey,
operation::{
AddDataArgs, CreateAccountArgs, KeyOperationArgs, Operation, RegisterServiceArgs,
ServiceChallenge, ServiceChallengeInput,
CreateAccountArgs, Operation, RegisterServiceArgs, ServiceChallenge, ServiceChallengeInput,
},
tree::{Digest, Hasher},
};

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
Expand Down Expand Up @@ -72,17 +72,19 @@ impl Hashchain {
pub fn create_account(
id: String,
value: VerifyingKey,
signature: Vec<u8>,
service_id: String,
challenge: ServiceChallengeInput,
prev_hash: Digest,
signature: Vec<u8>,
) -> Result<Hashchain> {
let mut hc = Hashchain::empty(id.clone());
let operation = Operation::CreateAccount(CreateAccountArgs {
id,
signature,
value,
service_id,
challenge,
prev_hash,
signature,
});
hc.perform_operation(operation)?;
Ok(hc)
Expand All @@ -93,6 +95,7 @@ impl Hashchain {
let operation = Operation::RegisterService(RegisterServiceArgs {
id,
creation_gate: challenge,
prev_hash: Digest::zero(),
});
hc.perform_operation(operation)?;
Ok(hc)
Expand Down Expand Up @@ -269,15 +272,16 @@ impl Hashchain {
&self.entries[idx]
}

pub fn last_hash(&self) -> Digest {
self.last().map_or(Digest::zero(), |entry| entry.hash)
}

fn push(&mut self, operation: Operation) -> Result<HashchainEntry> {
if operation.id() != self.id {
bail!("Operation ID does not match Hashchain ID");
}

let previous_hash = self
.entries
.last()
.map_or(Digest::new([0u8; 32]), |entry| entry.hash);
let previous_hash = self.last_hash();

let entry = HashchainEntry::new(operation, previous_hash);
self.entries.push(entry.clone());
Expand All @@ -293,31 +297,53 @@ impl Hashchain {
/// Verifies the structure and signature of a new operation
fn validate_new_operation(&self, operation: &Operation) -> Result<()> {
match operation {
Operation::RegisterService(_) => {
Operation::RegisterService(args) => {
if !self.entries.is_empty() {
bail!("RegisterService operation must be the first entry");
}

if args.prev_hash != Digest::zero() {
bail!("Previous hash for initial operation must be zero")
}

Ok(())
}
Operation::AddKey(KeyOperationArgs { signature, .. })
| Operation::RevokeKey(KeyOperationArgs { signature, .. })
| Operation::AddData(AddDataArgs {
op_signature: signature,
..
}) => {
let signing_key = self.get_key_at_index(signature.key_idx)?;
Operation::AddKey(args) | Operation::RevokeKey(args) => {
if args.prev_hash != self.last_hash() {
bail!("Previous hash for key operation must be the last hash")
}

let signing_key = self.get_key_at_index(args.signature.key_idx)?;

if self.is_key_revoked(signing_key.clone()) {
bail!("The signing key is revoked");
}

operation.verify_user_signature(signing_key.clone())
operation.verify_user_signature(signing_key)
}
Operation::AddData(args) => {
if args.prev_hash != self.last_hash() {
bail!("Previous hash for add-data operation must be the last hash")
}

let signing_key = self.get_key_at_index(args.op_signature.key_idx)?;

if self.is_key_revoked(signing_key.clone()) {
bail!("The signing key is revoked");
}

operation.verify_user_signature(signing_key)
}
Operation::CreateAccount(args) => {
if !self.entries.is_empty() {
bail!("RegisterService operation must be the first entry");
jns-ps marked this conversation as resolved.
Show resolved Hide resolved
}
operation.verify_user_signature(args.value.clone())

if args.prev_hash != Digest::zero() {
bail!("Previous hash for initial operation must be zero")
}

operation.verify_user_signature(&args.value)
jns-ps marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Expand Down
68 changes: 68 additions & 0 deletions crates/common/src/hasher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use jmt::SimpleHasher;
use serde::{ser::SerializeTupleStruct, Deserialize, Serialize};

#[derive(Debug, Clone, Default)]
pub struct Hasher(sha2::Sha256);

impl Hasher {
pub fn new() -> Self {
Self(sha2::Sha256::new())
}

pub fn update(&mut self, data: &[u8]) {
self.0.update(data);
}

pub fn finalize(self) -> [u8; 32] {
self.0.finalize()
}
}

impl Serialize for Hasher {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_tuple_struct("Sha256Wrapper", 0)?.end()
}
}

impl<'de> Deserialize<'de> for Hasher {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Sha256WrapperVisitor;

impl<'de> serde::de::Visitor<'de> for Sha256WrapperVisitor {
type Value = Hasher;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a Sha256Wrapper")
}

fn visit_seq<A>(self, _seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
Ok(Hasher::default())
}
}

deserializer.deserialize_tuple_struct("Sha256Wrapper", 0, Sha256WrapperVisitor)
}
}

impl SimpleHasher for Hasher {
fn new() -> Self {
Self::new()
}

fn update(&mut self, data: &[u8]) {
self.update(data);
}

fn finalize(self) -> [u8; 32] {
self.finalize()
}
}
jns-ps marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion crates/common/src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use secp256k1::{
use serde::{Deserialize, Serialize};
use std::{self};

use crate::tree::Digest;
use crate::digest::Digest;

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
/// Represents a public key supported by the system.
Expand Down
4 changes: 4 additions & 0 deletions crates/common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
pub mod digest;
pub mod hashchain;
pub mod hasher;
pub mod keys;
pub mod operation;
pub mod tree;

#[macro_use]
extern crate log;

#[cfg(feature = "test_utils")]
pub mod test_ops;
#[cfg(feature = "test_utils")]
pub mod test_utils;
Loading
Loading