Skip to content
Permalink
Browse files

Add multisig structure and ledger

  • Loading branch information...
vincenthz committed May 11, 2019
1 parent b7b97a6 commit 09998a99a7a042718bb2cb1b85e0cdc09e4c8082
@@ -21,6 +21,7 @@ pub mod key;
pub mod leadership;
pub mod ledger;
pub mod multiverse;
pub mod multisig;
pub mod setting;
pub mod stake;
pub mod transaction;
@@ -0,0 +1,115 @@
use crate::{account, key};
use chain_crypto::{PublicKey, Signature};

use super::index::{Index, TreeIndex, LEVEL_MAXLIMIT};
pub use crate::transaction::WitnessMultisigData;

/// Account Identifier (also used as Public Key)
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Identifier(key::Hash);

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

impl From<[u8; 32]> for Identifier {
fn from(a: [u8; 32]) -> Self {
Identifier(a.into())
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeclarationError {
ThresholdInvalid,
HasNotEnoughOwners,
HasTooManyOwners,
SubNotImplemented,
}

/// Declaration of a multisig account parameters which is:
///
/// * a threshold that need to be between 1 and the size of owners
/// * a bunch of owners which is either a hash of a key, or a sub declaration
#[derive(Debug, Clone)]
pub struct Declaration {
pub(crate) threshold: u8, // between 1 and len(owners)
pub(crate) owners: Vec<DeclElement>,
}

impl Declaration {
pub fn threshold(&self) -> usize {
self.threshold as usize
}

pub fn total(&self) -> usize {
self.owners.len()
}
}

#[derive(Debug, Clone)]
pub enum DeclElement {
Sub(Declaration),
Owner(key::Hash),
}

impl DeclElement {
pub fn to_hash(&self) -> key::Hash {
match self {
DeclElement::Sub(d) => d.to_identifier().0,
DeclElement::Owner(hash) => hash.clone(),
}
}

pub fn from_publickey(key: &PublicKey<account::AccountAlg>) -> Self {
DeclElement::Owner(key::Hash::hash_bytes(key.as_ref()))
}
}

// Create an identifier by concatenating the threshold (as a byte) and all the owners
// and returning the hash of this content
pub(super) fn owners_to_identifier(threshold: u8, owners: &[DeclElement]) -> Identifier {
let mut out = Vec::new();
out.extend_from_slice(&[threshold]);
for o in owners {
out.extend_from_slice(o.to_hash().as_ref())
}
Identifier(key::Hash::hash_bytes(&out))
}

impl Declaration {
/// Get the identifier associated with a declaration
pub fn to_identifier(&self) -> Identifier {
owners_to_identifier(self.threshold, &self.owners)
}

pub fn is_valid(&self) -> Result<(), DeclarationError> {
if self.threshold < 1 || self.threshold as usize > self.owners.len() {
return Err(DeclarationError::ThresholdInvalid);
}
if self.owners.len() <= 1 {
return Err(DeclarationError::HasNotEnoughOwners);
}
if self.owners.len() > LEVEL_MAXLIMIT {
return Err(DeclarationError::HasTooManyOwners);
}
Ok(())
}

pub fn get_path(&self, ti: TreeIndex) -> Option<(&Declaration, Index)> {
match ti {
TreeIndex::D1(idx) => Some((self, idx)),
TreeIndex::D2(r, idx) if r.to_usize() < self.owners.len() => {
match self.owners[r.to_usize()] {
DeclElement::Owner(_) => None,
DeclElement::Sub(ref d) => Some((d, idx)),
}
}
TreeIndex::D2(_, _) => None,
}
}
}

pub type Pk = PublicKey<account::AccountAlg>;
pub type Sig = Signature<WitnessMultisigData, account::AccountAlg>;
@@ -0,0 +1,94 @@
use std::cmp::Ordering;

pub const LEVEL_MAXLIMIT: usize = 8;

/// The Index is really just 3 bits and has a hardbound linked to the LEVEL_MAXLIMIT
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Index(u8);

impl Index {
pub fn from_u8(v: u8) -> Option<Self> {
if v as usize > LEVEL_MAXLIMIT {
None
} else {
Some(Index(v))
}
}

pub fn to_usize(self) -> usize {
self.0 as usize
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TreeIndex {
D1(Index),
D2(Index, Index),
}

impl PartialOrd for TreeIndex {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl Ord for TreeIndex {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(TreeIndex::D1(a), TreeIndex::D1(b)) => a.cmp(b),
(TreeIndex::D1(a), TreeIndex::D2(b1, _)) => a.cmp(b1).then(Ordering::Less),
(TreeIndex::D2(a1, _), TreeIndex::D1(b)) => a1.cmp(b).then(Ordering::Greater),
(TreeIndex::D2(a1, a2), TreeIndex::D2(b1, b2)) => a1.cmp(b1).then(a2.cmp(b2)),
}
}
}

const TREEINDEX_TAG_DEPTH1: u16 = 1;
const TREEINDEX_TAG_DEPTH2: u16 = 2;

impl TreeIndex {
pub fn indices(&self) -> Vec<Index> {
match self {
TreeIndex::D1(a) => vec![*a],
TreeIndex::D2(a, b) => vec![*a, *b],
}
}
pub fn depth(&self) -> usize {
match self {
TreeIndex::D1(_) => 0,
TreeIndex::D2(_, _) => 1,
}
}

pub fn pack(&self) -> u16 {
match self {
TreeIndex::D1(Index(a)) => (TREEINDEX_TAG_DEPTH1 << 12) + ((*a as u16) << 9),
TreeIndex::D2(Index(a), Index(b)) => {
(TREEINDEX_TAG_DEPTH2 << 12) + ((*a as u16) << 9) + ((*b as u16) << 6)
}
}
}

pub fn unpack(v: u16) -> Option<Self> {
let tag = (v >> 12) & 0b1111;
let a = (v >> 9) & 0b111;
let b = (v >> 6) & 0b111;
let c = (v >> 3) & 0b111;
let d = v & 0b111;

if c != 0 || d != 0 {
return None;
}

match tag {
TREEINDEX_TAG_DEPTH1 => {
if b != 0 {
return None;
}
Some(TreeIndex::D1(Index(a as u8)))
}
TREEINDEX_TAG_DEPTH2 => Some(TreeIndex::D2(Index(a as u8), Index(b as u8))),
_ => None,
}
}
}
@@ -0,0 +1,116 @@
use imhamt::{Hamt, InsertError, RemoveError};
use std::collections::hash_map::DefaultHasher;

use super::declaration::{Declaration, DeclarationError, Identifier};
use crate::accounting::account::{self, SpendingCounter};
use crate::value::Value;

#[derive(Clone)]
pub struct Ledger {
// TODO : investigate about merging the declarations and the accounts in
// one with an extension on the account::Ledger
accounts: account::Ledger<Identifier>,
declarations: Hamt<DefaultHasher, Identifier, Declaration>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LedgerError {
ParticipantOutOfBound,
AlreadyExist,
DoesntExist,
DeclarationError(DeclarationError),
AccountError(account::LedgerError),
IdentifierMismatch,
ThresholdNotMet,
}

impl From<account::LedgerError> for LedgerError {
fn from(a: account::LedgerError) -> Self {
LedgerError::AccountError(a)
}
}

impl From<InsertError> for LedgerError {
fn from(_: InsertError) -> Self {
LedgerError::AlreadyExist
}
}

impl From<DeclarationError> for LedgerError {
fn from(e: DeclarationError) -> Self {
LedgerError::DeclarationError(e)
}
}

impl From<RemoveError> for LedgerError {
fn from(_: RemoveError) -> Self {
LedgerError::DoesntExist
}
}

impl Ledger {
/// Create a new empty account ledger
pub fn new() -> Self {
Ledger {
accounts: account::Ledger::new(),
declarations: Hamt::new(),
}
}

/// Add a new multisig declaration into the ledger.
///
/// If the identifier is already present, error out.
pub fn add_account(&self, declaration: &Declaration) -> Result<Self, LedgerError> {
// check if declaration is valid here
declaration.is_valid()?;

let identifier = declaration.to_identifier();
let new_decls = self
.declarations
.insert(identifier.clone(), declaration.clone())?;
let new_accts = self.accounts.add_account(&identifier, Value::zero())?;
Ok(Self {
accounts: new_accts,
declarations: new_decls,
})
}

/// Remove a declaration from this ledger
pub fn remove_account(&self, ident: &Identifier) -> Result<Self, LedgerError> {
let new_decls = self.declarations.remove(ident)?;
let new_accts = self.accounts.remove_account(ident)?;
Ok(Self {
accounts: new_accts,
declarations: new_decls,
})
}

pub fn add_value(&self, identifier: &Identifier, value: Value) -> Result<Self, LedgerError> {
let new_accounts = self.accounts.add_value(identifier, value)?;
Ok(Self {
accounts: new_accounts,
declarations: self.declarations.clone(),
})
}

/// If the account doesn't exist, or that the value would become negative, errors out.
pub fn remove_value(
&self,
identifier: &Identifier,
value: Value,
) -> Result<(Self, &Declaration, SpendingCounter), LedgerError> {
let decl = self
.declarations
.lookup(identifier)
.ok_or(LedgerError::DoesntExist)?;
let (new_accts, spending_counter) = self.accounts.remove_value(identifier, value)?;
Ok((
Self {
accounts: new_accts,
declarations: self.declarations.clone(),
},
decl,
spending_counter,
))
}
}
Oops, something went wrong.

0 comments on commit 09998a9

Please sign in to comment.
You can’t perform that action at this time.