Skip to content
This repository was archived by the owner on Jun 6, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 9 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 {
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,
) -> 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,
pubkey: self.pubkey,
})
}
}

impl CounterpartyCommitted {
pub fn sign(
self,
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
2 changes: 1 addition & 1 deletion zkvm/src/signature/multikey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub struct Multikey {
impl Multikey {
pub fn new(pubkeys: Vec<VerificationKey>) -> Option<Self> {
// Create transcript for Multikey
let mut transcript = Transcript::new(b"ZkVM.aggregated-key");
let mut transcript = Transcript::new(b"MuSig.aggregated-key");
transcript.commit_u64(b"n", pubkeys.len() as u64);

// Commit pubkeys into the transcript
Expand Down
26 changes: 12 additions & 14 deletions zkvm/src/signature/musig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl Signature {
let mut transcript = transcript.clone();

// Make c = H(X, R, m)
// The message `m` should already have been fed into the transcript
// The message `m` has already been fed into the transcript
let c = {
transcript.commit_point(b"P", &P.0);
transcript.commit_point(b"R", &self.R.compress());
Expand All @@ -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 All @@ -57,8 +57,8 @@ mod tests {
let multikey = multikey_helper(&priv_keys).unwrap();

let expected_pubkey = CompressedRistretto::from_slice(&[
212, 211, 54, 88, 245, 166, 107, 207, 28, 70, 247, 28, 5, 233, 67, 112, 196, 30, 35,
136, 160, 232, 167, 109, 47, 88, 194, 207, 227, 71, 222, 102,
136, 53, 195, 229, 169, 217, 166, 146, 49, 19, 35, 5, 134, 41, 9, 177, 250, 77, 180,
47, 8, 211, 231, 80, 217, 222, 184, 242, 68, 53, 66, 13,
]);

assert_eq!(expected_pubkey, multikey.aggregated_key().0);
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
104 changes: 86 additions & 18 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 @@ -57,9 +57,8 @@ Input:
- pubkeys: `Vec<VerificationKey>`. This is a list of compressed public keys that will be aggregated, as long as they can be decompressed successfully.

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.)
- Create a new transcript using the tag "MuSig.aggregated-key".
- 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 All @@ -72,7 +71,7 @@ There are several paths to signing:
Function: `Signature::sign_single(...)`

Input:
- transcript: `&mut Transcript` - the message to be signed should have been committed to the transcript beforehand.
- transcript: `&mut Transcript` - a transcript to which the message to be signed has already been committed.
- privkey: `Scalar`

Operation:
Expand Down Expand Up @@ -107,15 +106,15 @@ There are several paths to signing:

### Verifying

Signature verificaiton happens in the `Signature::verify(...)` function.
Signature verification happens in the `Signature::verify(...)` function.

Input:
- `&self`
- transcript: `&mut Transcript` - the message to be signed should have been committed to the transcript beforehand.
- transcript: `&mut Transcript` - a transcript to which the signed message has already been committed.
- P: `VerificationKey`

Operation:
- Make `c = H(X, R, m)`. Since the transcript should already have had the message `m` committed to it, the function only needs to commit `X` with label "P" (for pubkey) and `R` with label "R", and then get the challenge scalar `c` with label "c".
- Make `c = H(X, R, m)`. Since the transcript already has the message `m` committed to it, the function only needs to commit `X` with label "P" (for pubkey) and `R` with label "R", and then get the challenge scalar `c` with label "c".
- Decompress verification key `P`. If this fails, return `Err(VMError::InvalidPoint)`.
- Check if `s * G == R + c * P`. `G` is the [base point](#base-point).

Expand Down Expand Up @@ -158,7 +157,7 @@ Fields: none
Function: `new(...)`

Input:
- transcript: `&mut Transcript` - the message to be signed should have been committed to the transcript beforehand.
- transcript: `&mut Transcript` - a transcript to which the message to be signed has already been committed.
- privkey: `Scalar`
- multikey: `Multikey`
- pubkeys: `Vec<VerificationKey>` - all the public keys that went into the multikey. The list is assumed to be in the same order as the upcoming lists of `NoncePrecommitment`s, `NonceCommitment`s, and `Share`s.
Expand All @@ -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