-
Notifications
You must be signed in to change notification settings - Fork 137
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add all comsig code, update to master branch
1 parent
b7104cd
commit 93f7fd5
Showing
12 changed files
with
1,477 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// Copyright 2023 The Grin Developers | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//! Onion modules for mxmixnet | ||
pub mod onion; | ||
pub mod types; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
// Copyright 2023 The Grin Developers | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//! Comsig modules for mxmixnet | ||
use secp256k1zkp::{self, pedersen::Commitment, ContextFlag, Secp256k1, SecretKey}; | ||
|
||
use blake2_rfc::blake2b::Blake2b; | ||
use byteorder::{BigEndian, ByteOrder}; | ||
use grin_core::ser::{self, Readable, Reader, Writeable, Writer}; | ||
use secp256k1zkp::rand::thread_rng; | ||
use thiserror::Error; | ||
|
||
/// A generalized Schnorr signature with a pedersen commitment value & blinding factors as the keys | ||
#[derive(Clone, Debug)] | ||
pub struct ComSignature { | ||
pub_nonce: Commitment, | ||
s: SecretKey, | ||
t: SecretKey, | ||
} | ||
|
||
/// Error types for Commitment Signatures | ||
#[derive(Error, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] | ||
pub enum ComSigError { | ||
#[error("Commitment signature is invalid")] | ||
InvalidSig, | ||
#[error("Secp256k1zkp error: {0:?}")] | ||
Secp256k1zkp(secp256k1zkp::Error), | ||
} | ||
|
||
impl From<secp256k1zkp::Error> for ComSigError { | ||
fn from(err: secp256k1zkp::Error) -> ComSigError { | ||
ComSigError::Secp256k1zkp(err) | ||
} | ||
} | ||
|
||
impl ComSignature { | ||
pub fn new(pub_nonce: &Commitment, s: &SecretKey, t: &SecretKey) -> ComSignature { | ||
ComSignature { | ||
pub_nonce: pub_nonce.to_owned(), | ||
s: s.to_owned(), | ||
t: t.to_owned(), | ||
} | ||
} | ||
|
||
#[allow(dead_code)] | ||
pub fn sign( | ||
amount: u64, | ||
blind: &SecretKey, | ||
msg: &Vec<u8>, | ||
) -> Result<ComSignature, ComSigError> { | ||
let secp = Secp256k1::with_caps(ContextFlag::Commit); | ||
|
||
let mut amt_bytes = [0; 32]; | ||
BigEndian::write_u64(&mut amt_bytes[24..32], amount); | ||
let k_amt = SecretKey::from_slice(&secp, &amt_bytes)?; | ||
|
||
let k_1 = SecretKey::new(&secp, &mut thread_rng()); | ||
let k_2 = SecretKey::new(&secp, &mut thread_rng()); | ||
|
||
let commitment = secp.commit(amount, blind.clone())?; | ||
let nonce_commitment = secp.commit_blind(k_1.clone(), k_2.clone())?; | ||
|
||
let e = ComSignature::calc_challenge(&secp, &commitment, &nonce_commitment, &msg)?; | ||
|
||
// s = k_1 + (e * amount) | ||
let mut s = k_amt.clone(); | ||
s.mul_assign(&secp, &e)?; | ||
s.add_assign(&secp, &k_1)?; | ||
|
||
// t = k_2 + (e * blind) | ||
let mut t = blind.clone(); | ||
t.mul_assign(&secp, &e)?; | ||
t.add_assign(&secp, &k_2)?; | ||
|
||
Ok(ComSignature::new(&nonce_commitment, &s, &t)) | ||
} | ||
|
||
#[allow(non_snake_case)] | ||
pub fn verify(&self, commit: &Commitment, msg: &Vec<u8>) -> Result<(), ComSigError> { | ||
let secp = Secp256k1::with_caps(ContextFlag::Commit); | ||
|
||
let S1 = secp.commit_blind(self.s.clone(), self.t.clone())?; | ||
|
||
let mut Ce = commit.to_pubkey(&secp)?; | ||
let e = ComSignature::calc_challenge(&secp, &commit, &self.pub_nonce, &msg)?; | ||
Ce.mul_assign(&secp, &e)?; | ||
|
||
let commits = vec![Commitment::from_pubkey(&secp, &Ce)?, self.pub_nonce.clone()]; | ||
let S2 = secp.commit_sum(commits, Vec::new())?; | ||
|
||
if S1 != S2 { | ||
return Err(ComSigError::InvalidSig); | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
fn calc_challenge( | ||
secp: &Secp256k1, | ||
commit: &Commitment, | ||
nonce_commit: &Commitment, | ||
msg: &Vec<u8>, | ||
) -> Result<SecretKey, ComSigError> { | ||
let mut challenge_hasher = Blake2b::new(32); | ||
challenge_hasher.update(&commit.0); | ||
challenge_hasher.update(&nonce_commit.0); | ||
challenge_hasher.update(msg); | ||
|
||
let mut challenge = [0; 32]; | ||
challenge.copy_from_slice(challenge_hasher.finalize().as_bytes()); | ||
|
||
Ok(SecretKey::from_slice(&secp, &challenge)?) | ||
} | ||
} | ||
|
||
/// Serializes a ComSignature to and from hex | ||
pub mod comsig_serde { | ||
use super::ComSignature; | ||
use grin_core::ser::{self, ProtocolVersion}; | ||
use grin_util::ToHex; | ||
use serde::{Deserialize, Serializer}; | ||
|
||
/// Serializes a ComSignature as a hex string | ||
pub fn serialize<S>(comsig: &ComSignature, serializer: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: Serializer, | ||
{ | ||
use serde::ser::Error; | ||
let bytes = ser::ser_vec(&comsig, ProtocolVersion::local()).map_err(Error::custom)?; | ||
serializer.serialize_str(&bytes.to_hex()) | ||
} | ||
|
||
/// Creates a ComSignature from a hex string | ||
pub fn deserialize<'de, D>(deserializer: D) -> Result<ComSignature, D::Error> | ||
where | ||
D: serde::Deserializer<'de>, | ||
{ | ||
use serde::de::Error; | ||
let bytes = String::deserialize(deserializer) | ||
.and_then(|string| grin_util::from_hex(&string).map_err(Error::custom))?; | ||
let sig: ComSignature = ser::deserialize_default(&mut &bytes[..]).map_err(Error::custom)?; | ||
Ok(sig) | ||
} | ||
} | ||
|
||
#[allow(non_snake_case)] | ||
impl Readable for ComSignature { | ||
fn read<R: Reader>(reader: &mut R) -> Result<Self, ser::Error> { | ||
let R = Commitment::read(reader)?; | ||
let s = super::secp::read_secret_key(reader)?; | ||
let t = super::secp::read_secret_key(reader)?; | ||
Ok(ComSignature::new(&R, &s, &t)) | ||
} | ||
} | ||
|
||
impl Writeable for ComSignature { | ||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> { | ||
writer.write_fixed_bytes(self.pub_nonce.0)?; | ||
writer.write_fixed_bytes(self.s.0)?; | ||
writer.write_fixed_bytes(self.t.0)?; | ||
Ok(()) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::{ComSigError, ComSignature, ContextFlag, Secp256k1, SecretKey}; | ||
|
||
use rand::Rng; | ||
use secp256k1zkp::rand::{thread_rng, RngCore}; | ||
|
||
/// Test signing and verification of ComSignatures | ||
#[test] | ||
fn verify_comsig() -> Result<(), ComSigError> { | ||
let secp = Secp256k1::with_caps(ContextFlag::Commit); | ||
|
||
let amount = thread_rng().next_u64(); | ||
let blind = SecretKey::new(&secp, &mut thread_rng()); | ||
let msg: [u8; 16] = rand::thread_rng().gen(); | ||
let comsig = ComSignature::sign(amount, &blind, &msg.to_vec())?; | ||
|
||
let commit = secp.commit(amount, blind.clone())?; | ||
assert!(comsig.verify(&commit, &msg.to_vec()).is_ok()); | ||
|
||
let wrong_msg: [u8; 16] = rand::thread_rng().gen(); | ||
assert!(comsig.verify(&commit, &wrong_msg.to_vec()).is_err()); | ||
|
||
let wrong_commit = secp.commit(amount, SecretKey::new(&secp, &mut thread_rng()))?; | ||
assert!(comsig.verify(&wrong_commit, &msg.to_vec()).is_err()); | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,293 @@ | ||
// Copyright 2023 The Grin Developers | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//! Dalek key wrapper for mwixnet primitives | ||
use super::secp::SecretKey; | ||
|
||
use ed25519_dalek::{Keypair, PublicKey, Signature, Signer, Verifier}; | ||
use grin_core::ser::{self, Readable, Reader, Writeable, Writer}; | ||
use grin_util::ToHex; | ||
use thiserror::Error; | ||
|
||
/// Error types for Dalek structures and logic | ||
#[derive(Clone, Error, Debug, PartialEq)] | ||
pub enum DalekError { | ||
/// Hex deser error | ||
#[error("Hex error {0:?}")] | ||
HexError(String), | ||
/// Key parsing error | ||
#[error("Failed to parse secret key")] | ||
KeyParseError, | ||
/// Error validating signature | ||
#[error("Failed to verify signature")] | ||
SigVerifyFailed, | ||
} | ||
|
||
/// Encapsulates an ed25519_dalek::PublicKey and provides (de-)serialization | ||
#[derive(Clone, Debug, PartialEq)] | ||
pub struct DalekPublicKey(PublicKey); | ||
|
||
impl DalekPublicKey { | ||
/// Convert DalekPublicKey to hex string | ||
pub fn to_hex(&self) -> String { | ||
self.0.to_hex() | ||
} | ||
|
||
/// Convert hex string to DalekPublicKey. | ||
pub fn from_hex(hex: &str) -> Result<Self, DalekError> { | ||
let bytes = grin_util::from_hex(hex) | ||
.map_err(|_| DalekError::HexError(format!("failed to decode {}", hex)))?; | ||
let pk = PublicKey::from_bytes(bytes.as_ref()) | ||
.map_err(|_| DalekError::HexError(format!("failed to decode {}", hex)))?; | ||
Ok(DalekPublicKey(pk)) | ||
} | ||
|
||
/// Compute DalekPublicKey from a SecretKey | ||
pub fn from_secret(key: &SecretKey) -> Self { | ||
let secret = ed25519_dalek::SecretKey::from_bytes(&key.0).unwrap(); | ||
let pk: PublicKey = (&secret).into(); | ||
DalekPublicKey(pk) | ||
} | ||
} | ||
|
||
impl AsRef<PublicKey> for DalekPublicKey { | ||
fn as_ref(&self) -> &PublicKey { | ||
&self.0 | ||
} | ||
} | ||
|
||
/// Serializes an Option<DalekPublicKey> to and from hex | ||
pub mod option_dalek_pubkey_serde { | ||
use super::DalekPublicKey; | ||
use grin_util::ToHex; | ||
use serde::de::Error; | ||
use serde::{Deserialize, Deserializer, Serializer}; | ||
|
||
/// | ||
pub fn serialize<S>(pk: &Option<DalekPublicKey>, serializer: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: Serializer, | ||
{ | ||
match pk { | ||
Some(pk) => serializer.serialize_str(&pk.0.to_hex()), | ||
None => serializer.serialize_none(), | ||
} | ||
} | ||
|
||
/// | ||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DalekPublicKey>, D::Error> | ||
where | ||
D: Deserializer<'de>, | ||
{ | ||
Option::<String>::deserialize(deserializer).and_then(|res| match res { | ||
Some(string) => DalekPublicKey::from_hex(&string) | ||
.map_err(|e| Error::custom(e.to_string())) | ||
.and_then(|pk: DalekPublicKey| Ok(Some(pk))), | ||
None => Ok(None), | ||
}) | ||
} | ||
} | ||
|
||
impl Readable for DalekPublicKey { | ||
fn read<R: Reader>(reader: &mut R) -> Result<Self, ser::Error> { | ||
let pk = PublicKey::from_bytes(&reader.read_fixed_bytes(32)?) | ||
.map_err(|_| ser::Error::CorruptedData)?; | ||
Ok(DalekPublicKey(pk)) | ||
} | ||
} | ||
|
||
impl Writeable for DalekPublicKey { | ||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> { | ||
writer.write_fixed_bytes(self.0.to_bytes())?; | ||
Ok(()) | ||
} | ||
} | ||
|
||
/// Encapsulates an ed25519_dalek::Signature and provides (de-)serialization | ||
#[derive(Clone, Debug, PartialEq)] | ||
pub struct DalekSignature(Signature); | ||
|
||
impl DalekSignature { | ||
/// Convert hex string to DalekSignature. | ||
pub fn from_hex(hex: &str) -> Result<Self, DalekError> { | ||
let bytes = grin_util::from_hex(hex) | ||
.map_err(|_| DalekError::HexError(format!("failed to decode {}", hex)))?; | ||
let sig = Signature::from_bytes(bytes.as_ref()) | ||
.map_err(|_| DalekError::HexError(format!("failed to decode {}", hex)))?; | ||
Ok(DalekSignature(sig)) | ||
} | ||
|
||
/// Verifies DalekSignature | ||
pub fn verify(&self, pk: &DalekPublicKey, msg: &[u8]) -> Result<(), DalekError> { | ||
pk.as_ref() | ||
.verify(&msg, &self.0) | ||
.map_err(|_| DalekError::SigVerifyFailed) | ||
} | ||
} | ||
|
||
impl AsRef<Signature> for DalekSignature { | ||
fn as_ref(&self) -> &Signature { | ||
&self.0 | ||
} | ||
} | ||
|
||
/// Serializes a DalekSignature to and from hex | ||
pub mod dalek_sig_serde { | ||
use super::DalekSignature; | ||
use grin_util::ToHex; | ||
use serde::de::Error; | ||
use serde::{Deserialize, Deserializer, Serializer}; | ||
|
||
/// | ||
pub fn serialize<S>(sig: &DalekSignature, serializer: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: Serializer, | ||
{ | ||
serializer.serialize_str(&sig.0.to_hex()) | ||
} | ||
|
||
/// | ||
pub fn deserialize<'de, D>(deserializer: D) -> Result<DalekSignature, D::Error> | ||
where | ||
D: Deserializer<'de>, | ||
{ | ||
let str = String::deserialize(deserializer)?; | ||
let sig = DalekSignature::from_hex(&str).map_err(|e| Error::custom(e.to_string()))?; | ||
Ok(sig) | ||
} | ||
} | ||
|
||
/// Dalek signature sign wrapper | ||
// TODO: This is likely duplicated throughout crate, check | ||
pub fn sign(sk: &SecretKey, message: &[u8]) -> Result<DalekSignature, DalekError> { | ||
let secret = | ||
ed25519_dalek::SecretKey::from_bytes(&sk.0).map_err(|_| DalekError::KeyParseError)?; | ||
let public: PublicKey = (&secret).into(); | ||
let keypair = Keypair { secret, public }; | ||
let sig = keypair.sign(&message); | ||
Ok(DalekSignature(sig)) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use crate::mwixnet::onion::test_util::rand_keypair; | ||
use grin_core::ser::{self, ProtocolVersion}; | ||
use grin_util::ToHex; | ||
use rand::Rng; | ||
use serde::{Deserialize, Serialize}; | ||
use serde_json::Value; | ||
|
||
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] | ||
struct TestPubKeySerde { | ||
#[serde(with = "option_dalek_pubkey_serde", default)] | ||
pk: Option<DalekPublicKey>, | ||
} | ||
|
||
#[test] | ||
fn pubkey_test() -> Result<(), Box<dyn std::error::Error>> { | ||
// Test from_hex | ||
let rand_pk = rand_keypair().1; | ||
let pk_from_hex = DalekPublicKey::from_hex(rand_pk.0.to_hex().as_str()).unwrap(); | ||
assert_eq!(rand_pk.0, pk_from_hex.0); | ||
|
||
// Test ser (de-)serialization | ||
let bytes = ser::ser_vec(&rand_pk, ProtocolVersion::local()).unwrap(); | ||
assert_eq!(bytes.len(), 32); | ||
let pk_from_deser: DalekPublicKey = ser::deserialize_default(&mut &bytes[..]).unwrap(); | ||
assert_eq!(rand_pk.0, pk_from_deser.0); | ||
|
||
// Test serde with Some(rand_pk) | ||
let some = TestPubKeySerde { | ||
pk: Some(rand_pk.clone()), | ||
}; | ||
let val = serde_json::to_value(some.clone()).unwrap(); | ||
if let Value::Object(o) = &val { | ||
if let Value::String(s) = o.get("pk").unwrap() { | ||
assert_eq!(s, &rand_pk.0.to_hex()); | ||
} else { | ||
panic!("Invalid type"); | ||
} | ||
} else { | ||
panic!("Invalid type") | ||
} | ||
assert_eq!(some, serde_json::from_value(val).unwrap()); | ||
|
||
// Test serde with empty pk field | ||
let none = TestPubKeySerde { pk: None }; | ||
let val = serde_json::to_value(none.clone()).unwrap(); | ||
if let Value::Object(o) = &val { | ||
if let Value::Null = o.get("pk").unwrap() { | ||
// ok | ||
} else { | ||
panic!("Invalid type"); | ||
} | ||
} else { | ||
panic!("Invalid type") | ||
} | ||
assert_eq!(none, serde_json::from_value(val).unwrap()); | ||
|
||
// Test serde with no pk field | ||
let none2 = serde_json::from_str::<TestPubKeySerde>("{}").unwrap(); | ||
assert_eq!(none, none2); | ||
|
||
Ok(()) | ||
} | ||
|
||
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] | ||
struct TestSigSerde { | ||
#[serde(with = "dalek_sig_serde")] | ||
sig: DalekSignature, | ||
} | ||
|
||
#[test] | ||
fn sig_test() -> Result<(), Box<dyn std::error::Error>> { | ||
// Sign a message | ||
let (sk, pk) = rand_keypair(); | ||
let msg: [u8; 16] = rand::thread_rng().gen(); | ||
let sig = sign(&sk, &msg).unwrap(); | ||
|
||
// Verify signature | ||
assert!(sig.verify(&pk, &msg).is_ok()); | ||
|
||
// Wrong message | ||
let wrong_msg: [u8; 16] = rand::thread_rng().gen(); | ||
assert!(sig.verify(&pk, &wrong_msg).is_err()); | ||
|
||
// Wrong pubkey | ||
let wrong_pk = rand_keypair().1; | ||
assert!(sig.verify(&wrong_pk, &msg).is_err()); | ||
|
||
// Test from_hex | ||
let sig_from_hex = DalekSignature::from_hex(sig.0.to_hex().as_str()).unwrap(); | ||
assert_eq!(sig.0, sig_from_hex.0); | ||
|
||
// Test serde (de-)serialization | ||
let serde_test = TestSigSerde { sig: sig.clone() }; | ||
let val = serde_json::to_value(serde_test.clone()).unwrap(); | ||
if let Value::Object(o) = &val { | ||
if let Value::String(s) = o.get("sig").unwrap() { | ||
assert_eq!(s, &sig.0.to_hex()); | ||
} else { | ||
panic!("Invalid type"); | ||
} | ||
} else { | ||
panic!("Invalid type") | ||
} | ||
assert_eq!(serde_test, serde_json::from_value(val).unwrap()); | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Copyright 2023 The Grin Developers | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//! Onion and comsig modules for mxmixnet | ||
pub mod comsig; | ||
pub mod dalek; | ||
pub mod secp; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// Copyright 2023 The Grin Developers | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//! SECP wrapper functions for onion/comsig | ||
//! TODO: Likely redundant stuff in here, trim | ||
pub use secp256k1zkp::aggsig; | ||
pub use secp256k1zkp::constants::{ | ||
AGG_SIGNATURE_SIZE, COMPRESSED_PUBLIC_KEY_SIZE, MAX_PROOF_SIZE, PEDERSEN_COMMITMENT_SIZE, | ||
SECRET_KEY_SIZE, | ||
}; | ||
pub use secp256k1zkp::ecdh::SharedSecret; | ||
pub use secp256k1zkp::key::{PublicKey, SecretKey, ZERO_KEY}; | ||
pub use secp256k1zkp::pedersen::{Commitment, RangeProof}; | ||
pub use secp256k1zkp::{ContextFlag, Message, Secp256k1, Signature}; | ||
|
||
use grin_core::ser::{self, Reader}; | ||
use secp256k1zkp::rand::thread_rng; | ||
|
||
/// Generate a random SecretKey. | ||
pub fn random_secret() -> SecretKey { | ||
let secp = Secp256k1::new(); | ||
SecretKey::new(&secp, &mut thread_rng()) | ||
} | ||
|
||
/// Deserialize a SecretKey from a Reader | ||
pub fn read_secret_key<R: Reader>(reader: &mut R) -> Result<SecretKey, ser::Error> { | ||
let buf = reader.read_fixed_bytes(SECRET_KEY_SIZE)?; | ||
let secp = Secp256k1::with_caps(ContextFlag::None); | ||
let pk = SecretKey::from_slice(&secp, &buf).map_err(|_| ser::Error::CorruptedData)?; | ||
Ok(pk) | ||
} | ||
|
||
/// Build a Pedersen Commitment using the provided value and blinding factor | ||
pub fn commit(value: u64, blind: &SecretKey) -> Result<Commitment, secp256k1zkp::Error> { | ||
let secp = Secp256k1::with_caps(ContextFlag::Commit); | ||
let commit = secp.commit(value, blind.clone())?; | ||
Ok(commit) | ||
} | ||
|
||
/// Add a blinding factor to an existing Commitment | ||
pub fn add_excess( | ||
commitment: &Commitment, | ||
excess: &SecretKey, | ||
) -> Result<Commitment, secp256k1zkp::Error> { | ||
let secp = Secp256k1::with_caps(ContextFlag::Commit); | ||
let excess_commit: Commitment = secp.commit(0, excess.clone())?; | ||
|
||
let commits = vec![commitment.clone(), excess_commit.clone()]; | ||
let sum = secp.commit_sum(commits, Vec::new())?; | ||
Ok(sum) | ||
} | ||
|
||
/// Subtracts a value (v*H) from an existing commitment | ||
pub fn sub_value(commitment: &Commitment, value: u64) -> Result<Commitment, secp256k1zkp::Error> { | ||
let secp = Secp256k1::with_caps(ContextFlag::Commit); | ||
let neg_commit: Commitment = secp.commit(value, ZERO_KEY)?; | ||
let sum = secp.commit_sum(vec![commitment.clone()], vec![neg_commit.clone()])?; | ||
Ok(sum) | ||
} | ||
|
||
/// Signs the message with the provided SecretKey | ||
pub fn sign(sk: &SecretKey, msg: &Message) -> Result<Signature, secp256k1zkp::Error> { | ||
let secp = Secp256k1::with_caps(ContextFlag::Full); | ||
let pubkey = PublicKey::from_secret_key(&secp, &sk)?; | ||
let sig = aggsig::sign_single(&secp, &msg, &sk, None, None, None, Some(&pubkey), None)?; | ||
Ok(sig) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
// Copyright 2023 The Grin Developers | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//! Onion module definition | ||
pub mod crypto; | ||
pub mod onion; | ||
pub mod util; | ||
|
||
use crypto::secp::{random_secret, Commitment, SecretKey}; | ||
use onion::{new_stream_cipher, Onion, OnionError, Payload, RawBytes}; | ||
|
||
use chacha20::cipher::StreamCipher; | ||
use grin_core::core::FeeFields; | ||
use secp256k1zkp::pedersen::RangeProof; | ||
use x25519_dalek::PublicKey as xPublicKey; | ||
use x25519_dalek::{SharedSecret, StaticSecret}; | ||
|
||
/// Onion hop struct | ||
#[derive(Clone)] | ||
pub struct Hop { | ||
/// Comsig server public key | ||
pub server_pubkey: xPublicKey, | ||
/// Kernel excess | ||
pub excess: SecretKey, | ||
/// Fee | ||
pub fee: FeeFields, | ||
/// Rangeproof | ||
pub rangeproof: Option<RangeProof>, | ||
} | ||
|
||
/// Crate a new hop | ||
pub fn new_hop( | ||
server_key: &SecretKey, | ||
hop_excess: &SecretKey, | ||
fee: u32, | ||
proof: Option<RangeProof>, | ||
) -> Hop { | ||
Hop { | ||
server_pubkey: xPublicKey::from(&StaticSecret::from(server_key.0.clone())), | ||
excess: hop_excess.clone(), | ||
fee: FeeFields::from(fee as u32), | ||
rangeproof: proof, | ||
} | ||
} | ||
|
||
/// Create an Onion for the Commitment, encrypting the payload for each hop | ||
pub fn create_onion(commitment: &Commitment, hops: &Vec<Hop>) -> Result<Onion, OnionError> { | ||
if hops.is_empty() { | ||
return Ok(Onion { | ||
ephemeral_pubkey: xPublicKey::from([0u8; 32]), | ||
commit: commitment.clone(), | ||
enc_payloads: vec![], | ||
}); | ||
} | ||
|
||
let mut shared_secrets: Vec<SharedSecret> = Vec::new(); | ||
let mut enc_payloads: Vec<RawBytes> = Vec::new(); | ||
let mut ephemeral_sk = StaticSecret::from(random_secret().0); | ||
let onion_ephemeral_pk = xPublicKey::from(&ephemeral_sk); | ||
for i in 0..hops.len() { | ||
let hop = &hops[i]; | ||
let shared_secret = ephemeral_sk.diffie_hellman(&hop.server_pubkey); | ||
shared_secrets.push(shared_secret); | ||
|
||
ephemeral_sk = StaticSecret::from(random_secret().0); | ||
let next_ephemeral_pk = if i < (hops.len() - 1) { | ||
xPublicKey::from(&ephemeral_sk) | ||
} else { | ||
xPublicKey::from([0u8; 32]) | ||
}; | ||
|
||
let payload = Payload { | ||
next_ephemeral_pk, | ||
excess: hop.excess.clone(), | ||
fee: hop.fee.clone(), | ||
rangeproof: hop.rangeproof.clone(), | ||
}; | ||
enc_payloads.push(payload.serialize()?); | ||
} | ||
|
||
for i in (0..shared_secrets.len()).rev() { | ||
let mut cipher = new_stream_cipher(&shared_secrets[i])?; | ||
for j in i..shared_secrets.len() { | ||
cipher.apply_keystream(&mut enc_payloads[j]); | ||
} | ||
} | ||
|
||
let onion = Onion { | ||
ephemeral_pubkey: onion_ephemeral_pk, | ||
commit: commitment.clone(), | ||
enc_payloads, | ||
}; | ||
Ok(onion) | ||
} | ||
|
||
/// Internal tests | ||
#[allow(missing_docs)] | ||
pub mod test_util { | ||
use super::*; | ||
use crypto::dalek::DalekPublicKey; | ||
use crypto::secp; | ||
|
||
use grin_core::core::hash::Hash; | ||
use grin_util::ToHex; | ||
use rand::{thread_rng, RngCore}; | ||
use secp256k1zkp::Secp256k1; | ||
|
||
pub fn rand_onion() -> Onion { | ||
let commit = rand_commit(); | ||
let mut hops = Vec::new(); | ||
let k = (thread_rng().next_u64() % 5) + 1; | ||
for i in 0..k { | ||
let rangeproof = if i == (k - 1) { | ||
Some(rand_proof()) | ||
} else { | ||
None | ||
}; | ||
let hop = new_hop( | ||
&random_secret(), | ||
&random_secret(), | ||
thread_rng().next_u32(), | ||
rangeproof, | ||
); | ||
hops.push(hop); | ||
} | ||
|
||
create_onion(&commit, &hops).unwrap() | ||
} | ||
|
||
pub fn rand_commit() -> Commitment { | ||
secp::commit(rand::thread_rng().next_u64(), &secp::random_secret()).unwrap() | ||
} | ||
|
||
pub fn rand_hash() -> Hash { | ||
Hash::from_hex(secp::random_secret().to_hex().as_str()).unwrap() | ||
} | ||
|
||
pub fn rand_proof() -> RangeProof { | ||
let secp = Secp256k1::new(); | ||
secp.bullet_proof( | ||
rand::thread_rng().next_u64(), | ||
secp::random_secret(), | ||
secp::random_secret(), | ||
secp::random_secret(), | ||
None, | ||
None, | ||
) | ||
} | ||
|
||
pub fn proof( | ||
value: u64, | ||
fee: u32, | ||
input_blind: &SecretKey, | ||
hop_excesses: &Vec<&SecretKey>, | ||
) -> (Commitment, RangeProof) { | ||
let secp = Secp256k1::new(); | ||
|
||
let mut blind = input_blind.clone(); | ||
for hop_excess in hop_excesses { | ||
blind.add_assign(&secp, &hop_excess).unwrap(); | ||
} | ||
|
||
let out_value = value - (fee as u64); | ||
|
||
let rp = secp.bullet_proof( | ||
out_value, | ||
blind.clone(), | ||
secp::random_secret(), | ||
secp::random_secret(), | ||
None, | ||
None, | ||
); | ||
|
||
(secp::commit(out_value, &blind).unwrap(), rp) | ||
} | ||
|
||
pub fn rand_keypair() -> (SecretKey, DalekPublicKey) { | ||
let sk = random_secret(); | ||
let pk = DalekPublicKey::from_secret(&sk); | ||
(sk, pk) | ||
} | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
// Copyright 2023 The Grin Developers | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//! Util fns for mwixnet | ||
//! TODO: possibly redundant, check or move elsewhere | ||
use grin_core::ser::{self, Readable, Reader, Writeable, Writer}; | ||
use std::convert::TryInto; | ||
|
||
/// Writes an optional value as '1' + value if Some, or '0' if None | ||
/// | ||
/// This function is used to serialize an optional value into a Writer. If the option | ||
/// contains Some value, it writes '1' followed by the serialized value. If the option | ||
/// is None, it just writes '0'. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `writer` - A Writer instance where the data will be written. | ||
/// * `o` - The Optional value that will be written. | ||
/// | ||
/// # Returns | ||
/// | ||
/// * If successful, returns Ok with nothing. | ||
/// * If an error occurs during writing, returns Err wrapping the error. | ||
/// | ||
/// # Example | ||
/// | ||
/// ``` | ||
/// use grin_wallet_libwallet::mwixnet::onion::util::write_optional; | ||
/// let mut writer:Vec<u8> = vec![]; | ||
/// let optional_value: Option<u32> = Some(10); | ||
/// //write_optional(&mut writer, &optional_value); | ||
/// ``` | ||
pub fn write_optional<O: Writeable, W: Writer>( | ||
writer: &mut W, | ||
o: &Option<O>, | ||
) -> Result<(), ser::Error> { | ||
match &o { | ||
Some(o) => { | ||
writer.write_u8(1)?; | ||
o.write(writer)?; | ||
} | ||
None => writer.write_u8(0)?, | ||
}; | ||
Ok(()) | ||
} | ||
|
||
/// Reads an optional value as '1' + value if Some, or '0' if None | ||
/// | ||
/// This function is used to deserialize an optional value from a Reader. If the first byte | ||
/// read is '0', it returns None. If the first byte is '1', it reads the next value and | ||
/// returns Some(value). | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `reader` - A Reader instance from where the data will be read. | ||
/// | ||
/// # Returns | ||
/// | ||
/// * If successful, returns Ok wrapping an optional value. If the first byte read was '0', | ||
/// returns None. If it was '1', returns Some(value). | ||
/// * If an error occurs during reading, returns Err wrapping the error. | ||
/// | ||
/// # Example | ||
/// | ||
/// ``` | ||
/// use grin_wallet_libwallet::mwixnet::onion::util::read_optional; | ||
/// use grin_core::ser::{BinReader, ProtocolVersion, DeserializationMode}; | ||
/// let mut buf: &[u8] = &[1, 0, 0, 0, 10]; | ||
/// let mut reader = BinReader::new(&mut buf, ProtocolVersion::local(), DeserializationMode::default()); | ||
/// let optional_value: Option<u32> = read_optional(&mut reader).unwrap(); | ||
/// assert_eq!(optional_value, Some(10)); | ||
/// ``` | ||
pub fn read_optional<O: Readable, R: Reader>(reader: &mut R) -> Result<Option<O>, ser::Error> { | ||
let o = if reader.read_u8()? == 0 { | ||
None | ||
} else { | ||
Some(O::read(reader)?) | ||
}; | ||
Ok(o) | ||
} | ||
|
||
/// Convert a vector to an array of size `S`. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `vec` - The input vector. | ||
/// | ||
/// # Returns | ||
/// | ||
/// * If successful, returns an `Ok` wrapping an array of size `S` containing | ||
/// the first `S` bytes of `vec`. | ||
/// * If `vec` is smaller than `S`, returns an `Err` indicating a count error. | ||
/// | ||
/// # Example | ||
/// | ||
/// ``` | ||
/// use grin_wallet_libwallet::mwixnet::onion::util::vec_to_array; | ||
/// let v = vec![0, 1, 2, 3, 4, 5]; | ||
/// let a = vec_to_array::<4>(&v).unwrap(); | ||
/// assert_eq!(a, [0, 1, 2, 3]); | ||
/// ``` | ||
pub fn vec_to_array<const S: usize>(vec: &Vec<u8>) -> Result<[u8; S], ser::Error> { | ||
if vec.len() < S { | ||
return Err(ser::Error::CountError); | ||
} | ||
let arr: [u8; S] = vec[0..S].try_into().unwrap(); | ||
Ok(arr) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use grin_core::ser::{BinReader, BinWriter, DeserializationMode, ProtocolVersion}; | ||
|
||
#[test] | ||
fn test_write_optional() { | ||
// Test with Some value | ||
let mut buf: Vec<u8> = vec![]; | ||
let val: Option<u32> = Some(10); | ||
write_optional(&mut BinWriter::default(&mut buf), &val).unwrap(); | ||
assert_eq!(buf, &[1, 0, 0, 0, 10]); // 1 for Some, then 10 as a little-endian u32 | ||
|
||
// Test with None value | ||
buf.clear(); | ||
let val: Option<u32> = None; | ||
write_optional(&mut BinWriter::default(&mut buf), &val).unwrap(); | ||
assert_eq!(buf, &[0]); // 0 for None | ||
} | ||
|
||
#[test] | ||
fn test_read_optional() { | ||
// Test with Some value | ||
let mut buf: &[u8] = &[1, 0, 0, 0, 10]; // 1 for Some, then 10 as a little-endian u32 | ||
let val: Option<u32> = read_optional(&mut BinReader::new( | ||
&mut buf, | ||
ProtocolVersion::local(), | ||
DeserializationMode::default(), | ||
)) | ||
.unwrap(); | ||
assert_eq!(val, Some(10)); | ||
|
||
// Test with None value | ||
buf = &[0]; // 0 for None | ||
let val: Option<u32> = read_optional(&mut BinReader::new( | ||
&mut buf, | ||
ProtocolVersion::local(), | ||
DeserializationMode::default(), | ||
)) | ||
.unwrap(); | ||
assert_eq!(val, None); | ||
} | ||
|
||
#[test] | ||
fn test_vec_to_array_success() { | ||
let v = vec![1, 2, 3, 4, 5, 6, 7, 8]; | ||
let a = vec_to_array::<4>(&v).unwrap(); | ||
assert_eq!(a, [1, 2, 3, 4]); | ||
} | ||
|
||
#[test] | ||
fn test_vec_to_array_too_small() { | ||
let v = vec![1, 2, 3]; | ||
let res = vec_to_array::<4>(&v); | ||
assert!(res.is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_vec_to_array_empty() { | ||
let v = vec![]; | ||
let res = vec_to_array::<4>(&v); | ||
assert!(res.is_err()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// Copyright 2024 The Grin Developers | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//! Types related to mwixnet requests required by rest of lib crate apis | ||
//! Should rexport all needed types here | ||
pub use super::onion::crypto::comsig::{self, ComSignature}; | ||
pub use super::onion::crypto::secp::{add_excess, random_secret}; | ||
pub use super::onion::onion::Onion; | ||
pub use super::onion::{new_hop, Hop}; | ||
use grin_util::secp::key::SecretKey; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
/// A Swap request | ||
#[derive(Serialize, Deserialize, Debug)] | ||
pub struct SwapReq { | ||
/// Com signature | ||
#[serde(with = "comsig::comsig_serde")] | ||
pub comsig: ComSignature, | ||
/// Onion | ||
pub onion: Onion, | ||
} | ||
|
||
/// mwixnetRequest Creation Params | ||
pub struct MixnetReqCreationParams { | ||
/// List of all the server keys | ||
pub server_keys: Vec<SecretKey>, | ||
/// Fees per hop | ||
pub fee_per_hop: u32, | ||
} |