Skip to content
This repository has been archived by the owner on Jun 6, 2024. It is now read-only.

MuSig: add counterparties #229

Merged
merged 15 commits into from
Mar 21, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions zkvm/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,11 @@ pub enum VMError {
#[fail(display = "Deferred point operations failed")]
PointOperationsFailed,

/// This error occurs when a signature share fails to verify
#[fail(display = "Share #{:?} failed to verify correctly", index)]
ShareError {
/// The index of the share that failed fo verify correctly
index: usize,
/// This error occurs when a MuSig signature share fails to verify
#[fail(display = "Share #{:?} failed to verify correctly", pubkey)]
MuSigShareError {
/// The pubkey corresponding to the MuSig share that failed fo verify correctly
pubkey: [u8; 32],
},

/// This error occurs when R1CS proof verification failed.
Expand Down
109 changes: 109 additions & 0 deletions zkvm/src/signature/counterparty.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use crate::errors::VMError;
use crate::signature::multikey::Multikey;
use crate::signature::VerificationKey;
use crate::transcript::TranscriptProtocol;
use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
use curve25519_dalek::ristretto::RistrettoPoint;
use curve25519_dalek::scalar::Scalar;
use merlin::Transcript;
use subtle::ConstantTimeEq;

#[derive(Copy, Clone)]
pub struct NoncePrecommitment([u8; 32]);

#[derive(Copy, Clone, Debug)]
pub struct NonceCommitment(RistrettoPoint);

impl NonceCommitment {
pub fn new(commitment: RistrettoPoint) -> Self {
NonceCommitment(commitment)
}

pub fn precommit(&self) -> NoncePrecommitment {
let mut h = Transcript::new(b"MuSig.nonce-precommit");
h.commit_point(b"R", &self.0.compress());
let mut precommitment = [0u8; 32];
h.challenge_bytes(b"precommitment", &mut precommitment);
NoncePrecommitment(precommitment)
}

pub fn sum(commitments: &Vec<Self>) -> RistrettoPoint {
cathieyun marked this conversation as resolved.
Show resolved Hide resolved
commitments.iter().map(|R_i| R_i.0).sum()
}
}

pub struct Counterparty {
pubkey: VerificationKey,
}

pub struct CounterpartyPrecommitted {
precommitment: NoncePrecommitment,
pubkey: VerificationKey,
}

pub struct CounterpartyCommitted {
commitment: NonceCommitment,
pubkey: VerificationKey,
}

impl Counterparty {
pub fn new(pubkey: VerificationKey) -> Self {
Counterparty { pubkey }
}

pub fn precommit_nonce(self, precommitment: NoncePrecommitment) -> CounterpartyPrecommitted {
CounterpartyPrecommitted {
precommitment,
pubkey: self.pubkey,
}
}
}

impl CounterpartyPrecommitted {
pub fn commit_nonce(
self,
commitment: &NonceCommitment,
cathieyun marked this conversation as resolved.
Show resolved Hide resolved
) -> Result<CounterpartyCommitted, VMError> {
// Check H(commitment) =? precommitment
let received_precommitment = commitment.precommit();
let equal = self.precommitment.0.ct_eq(&received_precommitment.0);
if equal.unwrap_u8() == 0 {
return Err(VMError::MuSigShareError {
pubkey: self.pubkey.0.to_bytes(),
});
}

Ok(CounterpartyCommitted {
commitment: *commitment,
cathieyun marked this conversation as resolved.
Show resolved Hide resolved
pubkey: self.pubkey,
})
}
}

impl CounterpartyCommitted {
pub fn sign(
&self,
cathieyun marked this conversation as resolved.
Show resolved Hide resolved
share: Scalar,
challenge: Scalar,
multikey: &Multikey,
) -> Result<Scalar, VMError> {
// Check if s_i * G == R_i + c * a_i * X_i.
// s_i = share
// G = RISTRETTO_BASEPOINT_POINT
// R_i = self.commitment
// c = challenge
// a_i = multikey.factor_for_key(self.pubkey)
// X_i = self.pubkey
let S_i = share * RISTRETTO_BASEPOINT_POINT;
let a_i = multikey.factor_for_key(&self.pubkey);
let X_i = self.pubkey.0.decompress().ok_or(VMError::InvalidPoint)?;

if S_i != self.commitment.0 + challenge * a_i * X_i {
return Err(VMError::MuSigShareError {
pubkey: self.pubkey.0.to_bytes(),
});
}

Ok(share)
}
}
3 changes: 2 additions & 1 deletion zkvm/src/signature/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ use crate::errors::VMError;
use crate::point_ops::PointOp;
use crate::transcript::TranscriptProtocol;

mod counterparty;
mod multikey;
mod musig;
mod prover;
mod signer;

/// Verification key (aka "pubkey") is a wrapper type around a Ristretto point
/// that lets the verifier to check the signature.
Expand Down
20 changes: 9 additions & 11 deletions zkvm/src/signature/musig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ mod tests {

use super::*;
use crate::errors::VMError;
use crate::signature::prover::*;
use crate::signature::signer::*;
use crate::signature::{multikey::Multikey, VerificationKey};
use curve25519_dalek::ristretto::CompressedRistretto;

Expand Down Expand Up @@ -90,17 +90,21 @@ mod tests {
}

fn sign_helper(
priv_keys: Vec<Scalar>,
privkeys: Vec<Scalar>,
multikey: Multikey,
m: Vec<u8>,
) -> Result<Signature, VMError> {
let mut transcript = Transcript::new(b"signing test");
transcript.commit_bytes(b"message", &m);
let pubkeys: Vec<_> = privkeys
.iter()
.map(|privkey| VerificationKey((privkey * RISTRETTO_BASEPOINT_POINT).compress()))
.collect();

let (parties, precomms): (Vec<_>, Vec<_>) = priv_keys
let (parties, precomms): (Vec<_>, Vec<_>) = privkeys
.clone()
.into_iter()
.map(|x_i| Party::new(&transcript.clone(), x_i, multikey.clone()))
.map(|x_i| Party::new(&transcript.clone(), x_i, multikey.clone(), pubkeys.clone()))
.unzip();

let (parties, comms): (Vec<_>, Vec<_>) = parties
Expand All @@ -113,15 +117,9 @@ mod tests {
.map(|p| p.receive_commitments(comms.clone()).unwrap())
.unzip();

let pub_keys: Vec<_> = priv_keys
.iter()
.map(|priv_key| VerificationKey((priv_key * RISTRETTO_BASEPOINT_POINT).compress()))
.collect();
let signatures: Vec<_> = parties
.into_iter()
.map(|p: PartyAwaitingShares| {
p.receive_shares(shares.clone(), pub_keys.clone()).unwrap()
})
.map(|p: PartyAwaitingShares| p.receive_shares(shares.clone()).unwrap())
.collect();

// Check that signatures from all parties are the same
Expand Down
92 changes: 80 additions & 12 deletions zkvm/src/signature/signature.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ Fields:

Functions:
- `Multikey::new(...)`: detailed more in [key aggregation](#key-aggregation) section.
- `Multikey::factor_for_key(self, verification_key)`: computes the `a_i` factor, where `a_i = H(<L>, X_i)`. `<L>`, or the list of pukeys that go into the aggregated pubkey, has already been committed into `self.transcript`. Therefore, this function simply clones `self.transcript`, commits the verification key (`X_i`) into the transcript with label "X_i", and then squeezes the challenge scalar `a_i` from the transcript with label "a_i".
- `Multikey::aggregated_key(self)`: returns the aggregated key stored in the multikey, `self.aggregated_key`.
- `Multikey::factor_for_key(&self, &verification_key)`: computes the `a_i` factor, where `a_i = H(<L>, X_i)`. `<L>`, or the list of pukeys that go into the aggregated pubkey, has already been committed into `self.transcript`. Therefore, this function simply clones `self.transcript`, commits the verification key (`X_i`) into the transcript with label "X_i", and then squeezes the challenge scalar `a_i` from the transcript with label "a_i".
- `Multikey::aggregated_key(&self)`: returns the aggregated key stored in the multikey, `self.aggregated_key`.

### Signature

Expand All @@ -59,7 +59,6 @@ Input:
Operation:
- Create a new transcript using the tag "ZkVM.aggregated-key". (TODO: remove the "ZkVM." if we make the signature crate separate from the ZkVM crate.)
- Commit all the pubkeys to the transcript. The transcript state corresponds to the commitment `<L>` in the MuSig paper: `<L> = H(X_1 || X_2 || ... || X_n)`.
(TODO: this sounds awkward, explain better?)
- Create `aggregated_key = sum_i ( a_i * X_i )`. Iterate over the pubkeys, compute the factor `a_i = H(<L>, X_i)`, and add `a_i * X_i` to the aggregated key.

Output:
Expand Down Expand Up @@ -167,7 +166,7 @@ Operation:
- Use the transcript to generate a random factor (the nonce), by committing to the privkey and passing in a `thread_rng`.
- Use the nonce to create a nonce commitment and precommitment
- Clone the transcript
- Create a vector of `Counterparty`s using the pubkeys.
- Create a vector of `Counterparty`s by calling `Counterparty::new(...)` with the input pubkeys.

Output:
- The next state in the protocol: `PartyAwaitingPrecommitments`
Expand Down Expand Up @@ -245,25 +244,94 @@ Output

## Protocol for counterparty state transitions
Counterparties are states stored internally by a party, that represent the messages received by from its counterparties.
TODO: add more description

Counterparty state transitions overview:
```
Counterparty{pubkey: Verificationkey}
Counterparty{pubkey}
.precommit_nonce(H: NoncePrecommitment) // simply adds precommitment
.precommit_nonce(precommitment)
CounterpartyPrecommitted{H, pubkey}
CounterpartyPrecommitted{precommitment, pubkey}
.commit_nonce(R: NonceCommitment) // verifies hash(R) == H
.commit_nonce(commitment)
CounterpartyCommitted{R, pubkey}
CounterpartyCommitted{commitment, pubkey}
.sign(s: Share) // verifies s_i * G == R_i + c * factor * pubkey_i
.sign(share, challenge, multikey)
s
s_i

s_total = sum{s_i}
R_total = sum{R_i}
Signature = {s: s_total, R: R_total}
```

### Counterparty

Fields: pubkey

Function: `new(...)`

Input:
- pubkey: `VerificationKey`

Operation:
- Create a new `Counterparty` instance with the input pubkey in the `pubkey` field

Output:
- The new `Counterparty` instance



Function: `precommit_nonce(...)`

Input:
- precommitment: `NoncePrecommitment`

Operation:
- Create a new `CounterpartyPrecommitted` instance with `self.pubkey` and the precommitment
- Future work: receive pubkey in this function, and match against stored counterparties to make sure the pubkey corresponds. This will allow us to receive messages out of order, and do sorting on the party's end.

Output:
- `CounterpartyPrecommitted`

### CounterpartyPrecommitted

Fields:
- precommitment: `NoncePrecommitment`
- pubkey: `VerificationKey`

Function: `commit_nonce(...)`

Input:
- commitment: `NonceCommitment`

Operation:
- Verify that `self.precommitment = commitment.precommit()`.
- If verification succeeds, create a new `CounterpartyCommitted` using `self.pubkey` and commitment.
- Else, return `Err(VMError::MuSigShareError)`.

Output:
- `Result<CounterpartyCommitted, MuSigShareError>`.

### CounterpartyCommitted

Fields:
- commitment: `NonceCommitment`
- pubkey: `VerificationKey`

Function: `sign(...)`

Input:
- share: `Scalar`
- challenge: `Scalar`
- multikey: `&Multikey`

Operation:
- Verify that `s_i * G == R_i + c * a_i * X_i`.
`s_i` = share, `G` = [base point](#base-point), `R_i` = self.commitment, `c` = challenge, `a_i` = `multikey.factor_for_key(self.pubkey)`, `X_i` = self.pubkey.
- If verification succeeds, return `Ok(share)`
- Else, return `Err(VMError::MuSigShareError)`

Output:
- `Result<Scalar, VMError>`
Loading