Skip to content
Permalink
Browse files

Merge pull request #679 from input-output-hk/multisig

Add ledger support for Multisig
  • Loading branch information...
vincenthz committed May 11, 2019
2 parents 2647e51 + bb2a8ee commit e0616f13bebd6b908320bddb1c1502dea0d3305a
@@ -64,6 +64,7 @@ pub enum Kind {
Single(PublicKey<Ed25519Extended>),
Group(PublicKey<Ed25519Extended>, PublicKey<Ed25519Extended>),
Account(PublicKey<Ed25519Extended>),
Multisig([u8; 32]),
}

/// Kind Type of an address
@@ -72,6 +73,7 @@ pub enum KindType {
Single,
Group,
Account,
Multisig,
}

/// Size of a Single address
@@ -83,18 +85,23 @@ pub const ADDR_SIZE_GROUP: usize = 65;
/// Size of an Account address
pub const ADDR_SIZE_ACCOUNT: usize = 33;

/// Size of an Multisig Account address
pub const ADDR_SIZE_MULTISIG: usize = 33;

const ADDR_KIND_LOW_SENTINEL: u8 = 0x2; /* anything under or equal to this is invalid */
pub const ADDR_KIND_SINGLE: u8 = 0x3;
pub const ADDR_KIND_GROUP: u8 = 0x4;
pub const ADDR_KIND_ACCOUNT: u8 = 0x5;
const ADDR_KIND_SENTINEL: u8 = 0x6; /* anything above or equal to this is invalid */
pub const ADDR_KIND_MULTISIG: u8 = 0x6;
const ADDR_KIND_SENTINEL: u8 = 0x7; /* anything above or equal to this is invalid */

impl KindType {
pub fn to_value(&self) -> u8 {
match self {
KindType::Single => ADDR_KIND_SINGLE,
KindType::Group => ADDR_KIND_GROUP,
KindType::Account => ADDR_KIND_ACCOUNT,
KindType::Multisig => ADDR_KIND_MULTISIG,
}
}
}
@@ -152,6 +159,7 @@ impl From<bech32::Error> for Error {
impl Address {
/// Try to convert from_bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
// check the kind is valid and length
is_valid_data(bytes)?;

let discr = get_discrimination_value(bytes[0]);
@@ -170,6 +178,11 @@ impl Address {
let stake_key = PublicKey::from_binary(&bytes[1..])?;
Kind::Account(stake_key)
}
ADDR_KIND_MULTISIG => {
let mut hash = [0u8; 32];
hash.copy_from_slice(&bytes[1..33]);
Kind::Multisig(hash)
}
_ => unreachable!(),
};
Ok(Address(discr, kind))
@@ -181,6 +194,7 @@ impl Address {
Kind::Single(_) => ADDR_SIZE_SINGLE,
Kind::Group(_, _) => ADDR_SIZE_GROUP,
Kind::Account(_) => ADDR_SIZE_ACCOUNT,
Kind::Multisig(_) => ADDR_SIZE_MULTISIG,
}
}

@@ -190,6 +204,7 @@ impl Address {
Kind::Single(_) => KindType::Single,
Kind::Group(_, _) => KindType::Group,
Kind::Account(_) => KindType::Account,
Kind::Multisig(_) => KindType::Multisig,
}
}

@@ -222,6 +237,7 @@ impl Address {
Kind::Single(ref pk) => Some(pk),
Kind::Group(ref pk, _) => Some(pk),
Kind::Account(ref pk) => Some(pk),
Kind::Multisig(_) => None,
}
}
}
@@ -265,6 +281,12 @@ fn is_valid_data(bytes: &[u8]) -> Result<(Discrimination, KindType), Error> {
}
KindType::Account
}
ADDR_KIND_MULTISIG => {
if bytes.len() != ADDR_SIZE_MULTISIG {
return Err(Error::InvalidAddress);
}
KindType::Multisig
}
_ => return Err(Error::InvalidKind),
};
Ok((get_discrimination_value(bytes[0]), kty))
@@ -355,6 +377,7 @@ impl PropertySerialize for Address {
codec.write_all(group.as_ref())?;
}
Kind::Account(stake_key) => codec.write_all(stake_key.as_ref())?,
Kind::Multisig(hash) => codec.write_all(&hash[..])?,
};

Ok(())
@@ -408,6 +431,11 @@ impl property::Deserialize for Address {
})?;
Kind::Account(stake_key)
}
ADDR_KIND_MULTISIG => {
let mut bytes = [0u8; 32];
codec.read_exact(&mut bytes)?;
Kind::Multisig(bytes)
}
_ => unreachable!(),
};
Ok(Address(discr, kind))
@@ -447,6 +475,10 @@ impl Readable for Address {
let stake_key = PublicKey::from_binary(&bytes[..]).map_err(chain_crypto_err)?;
Kind::Account(stake_key)
}
ADDR_KIND_MULTISIG => {
let bytes = <[u8; 32]>::read(buf)?;
Kind::Multisig(bytes)
}
n => return Err(ReadError::UnknownTag(n as u32)),
};
Ok(Address(discr, kind))
@@ -13,10 +13,11 @@ impl Arbitrary for Discrimination {

impl Arbitrary for KindType {
fn arbitrary<G: Gen>(g: &mut G) -> Self {
match u8::arbitrary(g) % 3 {
match u8::arbitrary(g) % 4 {
0 => KindType::Single,
1 => KindType::Group,
2 => KindType::Account,
3 => KindType::Multisig,
_ => unreachable!(),
}
}
@@ -34,6 +35,13 @@ impl Arbitrary for Address {
KindType::Single => Kind::Single(Arbitrary::arbitrary(g)),
KindType::Group => Kind::Group(Arbitrary::arbitrary(g), Arbitrary::arbitrary(g)),
KindType::Account => Kind::Account(Arbitrary::arbitrary(g)),
KindType::Multisig => {
let mut h = [0u8; 32];
for i in h.iter_mut() {
*i = Arbitrary::arbitrary(g)
}
Kind::Multisig(h)
}
};
Address(discrimination, kind)
}
@@ -0,0 +1,101 @@
# Multisig

This document defines the extension to the current protocol to support multisig accounts.
Multisig accounts are similar to normal account, except they are controlled by 2 or more
parties, and have a threshold for operation.

For example some use cases of account purpose:

* Alice, and Bob have an account when both their signatures are needed to do any operation.
* Alice, Bob and Charles have an account where any valid 2 out of 3 of their signatures is required to do any operation.
* The CEO, CFO, and one of 3 engineering directors are needed to do any operation.
* The CEO, or the CFO, or 2 of the 3 engineering directors are needed to do any operation.

## Communication

This documentation doesn't cover the out-of-band communication that need to happens
between parties of a multisig account, for registration and creation of transactions.

We assume participants a multisig account, are able to communicate on a channel.
The channel should be secure against tempering related to the endpoint, but
doesn't require any privacy to be secure. i.e. communication can be
eavedropped, but the data should be authenticated between parties.

Note that this requirement is not as strong during transaction creation,
other mechanisms should catch issues related to malicious parties.

## Threshold Identification Merkle Tree (TIMT)

We use the following primitive construction to describe a basic multisig elements

* Threshold: an unsigned integer between 1 to the number of keys.
* A sequence of public participant of this tree, which are either a public key or a TIMT Identifier for recursive scheme

Effectively:

TIMT<N> = Threshold x [Type x Hash; N]

We construct a unique identifier out of the following:

Identifier(TIMT<N>) = H(TIMT.Threshold | TIMT.Hash[0] | .. | TIMT.Hash[N-1])

To construct a valid witness related to this construction, one need to
provide at least minimum of Threshold signature elements for a given message with their respective
indices:

sign(TIMT<N>, Message) = [ Index x Signature[Index]; E ]
where TIMT.Threshold <= E <= TIMT.N

To validate we need to make sure that the number of participants is correct
for each level, validating all cryptographic signatures for non-TIMT participants,
and recursively re-building the identifier of the TIMT.

validate(signatures, TIMT<N>, Message) =
TIMT.Threshold <= #signatures <= TIMT.N
&& for all { signatures where signature.type is not TIMT }:
validate(sig, message) == true
&& Identifier(TIMT) == Identifier(fill_or_replace(TIMT, signature))

fill_or_replace(signature, TIMT<N>) =
For i in TIMT.N:
if signature.index == Present:
TIMT.hash[I] = signature.sighash

The individual signature is of the form:

sign(individual, message) =
individual.public-key x individual.sign(individual.secret, message)

## Registration

Multisig accounts can be created using a multisig registration certificate.
The certificate contains one to multiple TIMT. Each TIMT need to be individually valid.

Some limits of the maximum size of individual elements, and the overall scheme need
to be enforced.

Practically, the scheme becomes already complicated from a UX point of view
when reaching 2 levels, so we enforce an arbitrary limit of 2 levels, and that
each tree should have a maximum of 8 participants.

## Identification

The Identification of a multisig account is the Identifier of the toplevel TIMT.
A special identifier for multisig account is reserved in the address scheme to address
multisig account and differentiate them from normal account.

TODO: Add format

## Transaction

A special multisig witness is added to transaction input validation. Each individual bits
of the witness needs to be ordered by their respective tree index.

TODO: Add format

## Staking & Ownership

Multisig account can, just like normal accounts (group key), have their stake
delegated through the stake delegation certificate, and register stake pool as
one of the owner. The same mechanism used for transaction witnessing is
used
@@ -202,6 +202,12 @@ impl Hash {
}
}

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

impl property::Serialize for Hash {
type Error = std::io::Error;
fn serialize<W: std::io::Write>(&self, mut writer: W) -> Result<(), Self::Error> {
Oops, something went wrong.

0 comments on commit e0616f1

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