Skip to content

Commit 7de8647

Browse files
committed
Add primitives to sign/verify arbitrary messages (#4655)
1 parent 82b9943 commit 7de8647

File tree

3 files changed

+224
-0
lines changed

3 files changed

+224
-0
lines changed

ironfish-rust/src/errors.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ pub enum IronfishErrorKind {
6565
RandomnessError,
6666
RoundTwoSigningFailure,
6767
TryFromInt,
68+
Unsupported,
6869
Utf8,
6970
}
7071

ironfish-rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub mod rolling_filter;
1717
pub mod sapling_bls12;
1818
pub mod serializing;
1919
pub mod signal_catcher;
20+
pub mod signing;
2021
pub mod transaction;
2122
pub mod util;
2223
pub mod witness;

ironfish-rust/src/signing.rs

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4+
5+
//! Methods to sign arbitrary message using Iron Fish keys. Note that this module cannot be used to
6+
//! sign or verify transactions.
7+
//!
8+
//! The signature scheme used is RedDSA on the JubJub curve (RedJubJub). Signatures are produced
9+
//! using the [Incoming View Key](crate::keys::IncomingViewKey) and verified using the [Public
10+
//! Address](crate::keys::PublicAddress).
11+
//!
12+
//! See [`ironfish_rust::transaction`] for methods to sign/verify transactions.
13+
//!
14+
//! # Examples
15+
//!
16+
//! ```
17+
//! use ironfish::keys::SaplingKey;
18+
//! use ironfish::signing::sign_message;
19+
//! use ironfish::signing::verify_message;
20+
//!
21+
//! // Generate keys for signing
22+
//! let secret_key = SaplingKey::generate_key();
23+
//!
24+
//! // Sign a message
25+
//! let message = b"some arbitrary message";
26+
//! let signature = sign_message(&secret_key, message, rand::thread_rng());
27+
//!
28+
//! // Get the keys for verification
29+
//! let public_address = secret_key.public_address();
30+
//!
31+
//! // Verify the signature
32+
//! match verify_message(&public_address, message, &signature) {
33+
//! Ok(()) => println!("verification succeeded"),
34+
//! Err(err) => println!("verification failed: {err}"),
35+
//! }
36+
//! ```
37+
38+
use crate::{
39+
errors::{IronfishError, IronfishErrorKind},
40+
PublicAddress, SaplingKey,
41+
};
42+
use ironfish_zkp::{
43+
constants::PUBLIC_KEY_GENERATOR,
44+
redjubjub::{PrivateKey, PublicKey, Signature},
45+
};
46+
use rand::{CryptoRng, RngCore};
47+
use std::io;
48+
49+
const VERSION: u8 = 0xa7u8;
50+
51+
pub type MessageSignatureBytes = [u8; 65];
52+
53+
#[derive(Copy, Clone, Debug)]
54+
pub struct MessageSignature(Signature);
55+
56+
impl MessageSignature {
57+
pub fn to_bytes(&self) -> MessageSignatureBytes {
58+
let mut bytes = [0u8; 65];
59+
self.write(&mut bytes[..])
60+
.expect("serializing to an array of the correct size should never fail");
61+
bytes
62+
}
63+
64+
pub fn from_bytes(bytes: &MessageSignatureBytes) -> Self {
65+
Self::read(&bytes[..])
66+
.expect("deserializing an array of the correct size should never fail")
67+
}
68+
69+
pub fn read<R: io::Read>(mut reader: R) -> Result<Self, IronfishError> {
70+
let mut version = [0u8; 1];
71+
reader.read_exact(&mut version)?;
72+
if version != [VERSION] {
73+
return Err(IronfishError::new(IronfishErrorKind::Unsupported));
74+
}
75+
76+
let signature = Signature::read(reader)?;
77+
Ok(signature.into())
78+
}
79+
80+
pub fn write<W: io::Write>(&self, mut writer: W) -> Result<(), IronfishError> {
81+
writer.write_all(&[VERSION])?;
82+
self.0.write(writer)?;
83+
Ok(())
84+
}
85+
}
86+
87+
impl AsRef<Signature> for MessageSignature {
88+
#[inline]
89+
fn as_ref(&self) -> &Signature {
90+
&self.0
91+
}
92+
}
93+
94+
impl From<Signature> for MessageSignature {
95+
#[inline]
96+
fn from(signature: Signature) -> Self {
97+
Self(signature)
98+
}
99+
}
100+
101+
impl From<MessageSignature> for Signature {
102+
#[inline]
103+
fn from(signature: MessageSignature) -> Self {
104+
signature.0
105+
}
106+
}
107+
108+
impl From<MessageSignatureBytes> for MessageSignature {
109+
#[inline]
110+
fn from(bytes: MessageSignatureBytes) -> Self {
111+
Self::from_bytes(&bytes)
112+
}
113+
}
114+
115+
impl From<MessageSignature> for MessageSignatureBytes {
116+
#[inline]
117+
fn from(signature: MessageSignature) -> Self {
118+
signature.to_bytes()
119+
}
120+
}
121+
122+
fn signing_key(secret_key: &SaplingKey) -> PrivateKey {
123+
PrivateKey(secret_key.incoming_viewing_key.view_key)
124+
}
125+
126+
fn verification_key(public_address: &PublicAddress) -> PublicKey {
127+
PublicKey(public_address.0.into())
128+
}
129+
130+
/// Signs a message using the Incoming View Key.
131+
///
132+
/// The signature can be verified with [`verify_message()`] using the [Public
133+
/// Address](crate::keys::PublicAddress).
134+
pub fn sign_message<R: RngCore + CryptoRng>(
135+
secret_key: &SaplingKey,
136+
message: &[u8],
137+
mut rng: R,
138+
) -> MessageSignature {
139+
signing_key(secret_key)
140+
.sign(message, &mut rng, *PUBLIC_KEY_GENERATOR)
141+
.into()
142+
}
143+
144+
/// Verifies a signature produced by [`sign_message()`].
145+
pub fn verify_message(
146+
public_address: &PublicAddress,
147+
message: &[u8],
148+
signature: &MessageSignature,
149+
) -> Result<(), IronfishError> {
150+
match verification_key(public_address).verify(message, &signature.0, *PUBLIC_KEY_GENERATOR) {
151+
true => Ok(()),
152+
false => Err(IronfishError::new(IronfishErrorKind::InvalidSignature)),
153+
}
154+
}
155+
156+
#[cfg(test)]
157+
mod tests {
158+
use crate::{
159+
signing::{sign_message, verify_message, MessageSignature},
160+
SaplingKey,
161+
};
162+
use rand::thread_rng;
163+
164+
#[test]
165+
fn roundtrip() {
166+
let secret_key = SaplingKey::generate_key();
167+
let public_address = secret_key.public_address();
168+
let message = b"test message";
169+
170+
let signature = sign_message(&secret_key, message, thread_rng());
171+
verify_message(&public_address, message, &signature).expect("verification should pass");
172+
}
173+
174+
#[test]
175+
fn roundtrip_with_serialization() {
176+
let secret_key = SaplingKey::generate_key();
177+
let public_address = secret_key.public_address();
178+
let message = b"test message";
179+
180+
let signature = sign_message(&secret_key, message, thread_rng());
181+
let serialized_signature = signature.to_bytes();
182+
let deserialized_signature = MessageSignature::from_bytes(&serialized_signature);
183+
verify_message(&public_address, message, &deserialized_signature)
184+
.expect("verification should pass");
185+
}
186+
187+
#[test]
188+
fn non_determinism() {
189+
let secret_key = SaplingKey::generate_key();
190+
let message = b"test message";
191+
192+
// Sign the same message twice with the same key, and verify that the output is different
193+
// each time
194+
let signature1 = sign_message(&secret_key, message, thread_rng());
195+
let signature2 = sign_message(&secret_key, message, thread_rng());
196+
197+
assert_ne!(signature1.to_bytes(), signature2.to_bytes());
198+
}
199+
200+
#[test]
201+
fn verify_fails_with_wrong_message() {
202+
let secret_key = SaplingKey::generate_key();
203+
let public_address = secret_key.public_address();
204+
let message = b"test message";
205+
206+
let signature = sign_message(&secret_key, message, thread_rng());
207+
let wrong_message = b"another test message";
208+
verify_message(&public_address, wrong_message, &signature)
209+
.expect_err("verification should fail");
210+
}
211+
212+
#[test]
213+
fn verify_fails_with_wrong_public_address() {
214+
let secret_key = SaplingKey::generate_key();
215+
let message = b"test message";
216+
217+
let signature = sign_message(&secret_key, message, thread_rng());
218+
let wrong_public_address = SaplingKey::generate_key().public_address();
219+
verify_message(&wrong_public_address, message, &signature)
220+
.expect_err("verification should fail");
221+
}
222+
}

0 commit comments

Comments
 (0)