Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make schnorr sign/verify accept a message slice instead of 32 bytes Message #706

Merged
merged 1 commit into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -958,8 +958,8 @@ impl Keypair {
/// Constructs an schnorr signature for `msg` using the global [`SECP256K1`] context.
#[inline]
#[cfg(all(feature = "global-context", feature = "rand-std"))]
pub fn sign_schnorr(&self, msg: Message) -> schnorr::Signature {
SECP256K1.sign_schnorr(&msg, self)
pub fn sign_schnorr(&self, msg: &[u8]) -> schnorr::Signature {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure about this. On one hand Message worked like a lint on the other this API makes much more sense in the context of schnorr sigs. I'm tentatively ACK on this but I'll take more time to think about it and possibly hear other opinions.

Other than that, the PR looks OK.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Message forced the user to pre-hash their message, which is not required by the upstream API, has no security benefit, and cannot be worked around (it's not just a lint, it's a meaningful API restriction).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of having two methods or something. But I guess this is fine. I can't find a good enough reason to do it differently.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please publish this change soon. We'd rather not have to fork the crate to support BIP-340 signatures.
@ChristopherA

SECP256K1.sign_schnorr(msg, self)
}

/// Attempts to erase the secret within the underlying array.
Expand Down Expand Up @@ -1316,7 +1316,7 @@ impl XOnlyPublicKey {
pub fn verify<C: Verification>(
&self,
secp: &Secp256k1<C>,
msg: &Message,
msg: &[u8],
sig: &schnorr::Signature,
) -> Result<(), Error> {
secp.verify_schnorr(sig, msg, self)
Expand Down
242 changes: 222 additions & 20 deletions src/schnorr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ use core::{fmt, ptr, str};

#[cfg(feature = "rand")]
use rand::{CryptoRng, Rng};
use secp256k1_sys::SchnorrSigExtraParams;

use crate::ffi::{self, CPtr};
use crate::key::{Keypair, XOnlyPublicKey};
#[cfg(feature = "global-context")]
use crate::SECP256K1;
use crate::{constants, from_hex, Error, Message, Secp256k1, Signing, Verification};
use crate::{constants, from_hex, Error, Secp256k1, Signing, Verification};

/// Represents a schnorr signature.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
Expand Down Expand Up @@ -93,28 +94,30 @@ impl Signature {
/// Verifies a schnorr signature for `msg` using `pk` and the global [`SECP256K1`] context.
#[inline]
#[cfg(feature = "global-context")]
pub fn verify(&self, msg: &Message, pk: &XOnlyPublicKey) -> Result<(), Error> {
pub fn verify(&self, msg: &[u8], pk: &XOnlyPublicKey) -> Result<(), Error> {
SECP256K1.verify_schnorr(self, msg, pk)
}
}

impl<C: Signing> Secp256k1<C> {
fn sign_schnorr_helper(
&self,
msg: &Message,
msg: &[u8],
keypair: &Keypair,
nonce_data: *const ffi::types::c_uchar,
) -> Signature {
unsafe {
let mut sig = [0u8; constants::SCHNORR_SIGNATURE_SIZE];
let extra = SchnorrSigExtraParams::new(None, nonce_data.cast());
assert_eq!(
1,
ffi::secp256k1_schnorrsig_sign(
ffi::secp256k1_schnorrsig_sign_custom(
self.ctx.as_ptr(),
sig.as_mut_c_ptr(),
msg.as_c_ptr(),
msg.len(),
keypair.as_c_ptr(),
nonce_data,
&extra,
)
);

Expand All @@ -125,19 +128,19 @@ impl<C: Signing> Secp256k1<C> {
/// Creates a schnorr signature internally using the [`rand::rngs::ThreadRng`] random number
/// generator to generate the auxiliary random data.
#[cfg(feature = "rand-std")]
pub fn sign_schnorr(&self, msg: &Message, keypair: &Keypair) -> Signature {
pub fn sign_schnorr(&self, msg: &[u8], keypair: &Keypair) -> Signature {
self.sign_schnorr_with_rng(msg, keypair, &mut rand::thread_rng())
}

/// Creates a schnorr signature without using any auxiliary random data.
pub fn sign_schnorr_no_aux_rand(&self, msg: &Message, keypair: &Keypair) -> Signature {
pub fn sign_schnorr_no_aux_rand(&self, msg: &[u8], keypair: &Keypair) -> Signature {
self.sign_schnorr_helper(msg, keypair, ptr::null())
}

/// Creates a schnorr signature using the given auxiliary random data.
pub fn sign_schnorr_with_aux_rand(
&self,
msg: &Message,
msg: &[u8],
keypair: &Keypair,
aux_rand: &[u8; 32],
) -> Signature {
Expand All @@ -149,7 +152,7 @@ impl<C: Signing> Secp256k1<C> {
#[cfg(feature = "rand")]
pub fn sign_schnorr_with_rng<R: Rng + CryptoRng>(
&self,
msg: &Message,
msg: &[u8],
keypair: &Keypair,
rng: &mut R,
) -> Signature {
Expand All @@ -164,15 +167,15 @@ impl<C: Verification> Secp256k1<C> {
pub fn verify_schnorr(
&self,
sig: &Signature,
msg: &Message,
msg: &[u8],
pubkey: &XOnlyPublicKey,
) -> Result<(), Error> {
unsafe {
let ret = ffi::secp256k1_schnorrsig_verify(
self.ctx.as_ptr(),
sig.as_c_ptr(),
msg.as_c_ptr(),
32,
msg.len(),
pubkey.as_c_ptr(),
);

Expand Down Expand Up @@ -235,9 +238,7 @@ mod tests {
}

#[cfg(feature = "rand-std")]
fn sign_helper(
sign: fn(&Secp256k1<crate::All>, &Message, &Keypair, &mut ThreadRng) -> Signature,
) {
fn sign_helper(sign: fn(&Secp256k1<crate::All>, &[u8], &Keypair, &mut ThreadRng) -> Signature) {
let secp = Secp256k1::new();

let mut rng = rand::thread_rng();
Expand All @@ -246,7 +247,6 @@ mod tests {

for _ in 0..100 {
let msg = crate::random_32_bytes(&mut rand::thread_rng());
let msg = Message::from_digest_slice(&msg).unwrap();

let sig = sign(&secp, &msg, &kp, &mut rng);

Expand All @@ -260,8 +260,7 @@ mod tests {
fn schnorr_sign() {
let secp = Secp256k1::new();

let hex_msg = hex_32!("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614");
let msg = Message::from_digest_slice(&hex_msg).unwrap();
let msg = hex_32!("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614");
let sk = Keypair::from_seckey_str(
&secp,
"688C77BC2D5AAFF5491CF309D4753B732135470D05B7B2CD21ADD0744FE97BEF",
Expand All @@ -282,8 +281,7 @@ mod tests {
fn schnorr_verify() {
let secp = Secp256k1::new();

let hex_msg = hex_32!("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614");
let msg = Message::from_digest_slice(&hex_msg).unwrap();
let msg = hex_32!("E48441762FB75010B2AA31A512B62B4148AA3FB08EB0765D76B252559064A614");
let sig = Signature::from_str("6470FD1303DDA4FDA717B9837153C24A6EAB377183FC438F939E0ED2B620E9EE5077C4A8B8DCA28963D772A94F5F0DDF598E1C47C137F91933274C7C3EDADCE8").unwrap();
let pubkey = XOnlyPublicKey::from_str(
"B33CC9EDC096D0A83416964BD3C6247B8FECD256E4EFA7870D2C854BDEB33390",
Expand Down Expand Up @@ -462,7 +460,7 @@ mod tests {

let s = Secp256k1::new();

let msg = Message::from_digest_slice(&[1; 32]).unwrap();
let msg = [1; 32];
let keypair = Keypair::from_seckey_slice(&s, &[2; 32]).unwrap();
let aux = [3u8; 32];
let sig = s.sign_schnorr_with_aux_rand(&msg, &keypair, &aux);
Expand Down Expand Up @@ -506,4 +504,208 @@ mod tests {
assert_tokens(&pk.readable(), &[Token::Str(PK_STR)]);
assert_tokens(&pk.readable(), &[Token::String(PK_STR)]);
}

#[test]
#[cfg(feature = "alloc")]
#[cfg(not(secp256k1_fuzz))] // fixed sig vectors can't work with fuzz-sigs
fn bip340_test_vectors() {
struct TestVector {
secret_key: Option<[u8; 32]>,
public_key: [u8; 32],
aux_rand: Option<[u8; 32]>,
message: Vec<u8>,
signature: [u8; 64],
should_fail_verify: bool,
}
fn hex_arr<T: From<[u8; N]>, const N: usize>(s: &str) -> T {
let mut out = [0; N];
from_hex(s, &mut out).unwrap();
out.into()
}
let hex_vec = |s: &str| {
let mut v = vec![0u8; s.len() / 2];
from_hex(s, v.as_mut_slice()).unwrap();
v
};

let vectors = [
TestVector {
secret_key: hex_arr("0000000000000000000000000000000000000000000000000000000000000003"),
public_key: hex_arr("F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9"),
aux_rand: hex_arr("0000000000000000000000000000000000000000000000000000000000000000"),
message: hex_vec("0000000000000000000000000000000000000000000000000000000000000000"),
signature: hex_arr("E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0"),
should_fail_verify: false,
},
TestVector {
secret_key: hex_arr("B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF"),
public_key: hex_arr("DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"),
aux_rand: hex_arr("0000000000000000000000000000000000000000000000000000000000000001"),
message: hex_vec("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89"),
signature: hex_arr("6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A"),
should_fail_verify: false,
},
TestVector {
secret_key: hex_arr("C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9"),
public_key: hex_arr("DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8"),
aux_rand: hex_arr("C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906"),
message: hex_vec("7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C"),
signature: hex_arr("5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7"),
should_fail_verify: false,
},
TestVector {
secret_key: hex_arr("0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710"),
public_key: hex_arr("25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517"),
aux_rand: hex_arr("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
message: hex_vec("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
signature: hex_arr("7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3"),
should_fail_verify: false,
},
TestVector {
secret_key: None,
public_key: hex_arr("D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9"),
aux_rand: None,
message: hex_vec("4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703"),
signature: hex_arr("00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4"),
should_fail_verify: false,
},
TestVector {
secret_key: None,
public_key: hex_arr("EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34"),
aux_rand: None,
message: hex_vec("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89"),
signature: hex_arr("6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B"),
should_fail_verify: true,
},
TestVector {
secret_key: None,
public_key: hex_arr("DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"),
aux_rand: None,
message: hex_vec("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89"),
signature: hex_arr("FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2"),
should_fail_verify: true,
},
TestVector {
secret_key: None,
public_key: hex_arr("DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"),
aux_rand: None,
message: hex_vec("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89"),
signature: hex_arr("1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD"),
should_fail_verify: true,
},
TestVector {
secret_key: None,
public_key: hex_arr("DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"),
aux_rand: None,
message: hex_vec("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89"),
signature: hex_arr("6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6"),
should_fail_verify: true,
},
TestVector {
secret_key: None,
public_key: hex_arr("DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"),
aux_rand: None,
message: hex_vec("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89"),
signature: hex_arr("0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051"),
should_fail_verify: true,
},
TestVector {
secret_key: None,
public_key: hex_arr("DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"),
aux_rand: None,
message: hex_vec("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89"),
signature: hex_arr("00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197"),
should_fail_verify: true,
},
TestVector {
secret_key: None,
public_key: hex_arr("DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"),
aux_rand: None,
message: hex_vec("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89"),
signature: hex_arr("4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B"),
should_fail_verify: true,
},
TestVector {
secret_key: None,
public_key: hex_arr("DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"),
aux_rand: None,
message: hex_vec("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89"),
signature: hex_arr("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B"),
should_fail_verify: true,
},
TestVector {
secret_key: None,
public_key: hex_arr("DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"),
aux_rand: None,
message: hex_vec("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89"),
signature: hex_arr("6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"),
should_fail_verify: true,
},
TestVector {
secret_key: None,
public_key: hex_arr("778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117"),
aux_rand: None,
message: hex_vec("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89"),
signature: hex_arr("6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B"),
should_fail_verify: true,
},
TestVector {
secret_key: hex_arr("0340034003400340034003400340034003400340034003400340034003400340"),
public_key: hex_arr("778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117"),
aux_rand: hex_arr("0000000000000000000000000000000000000000000000000000000000000000"),
message: hex_vec(""),
signature: hex_arr("71535DB165ECD9FBBC046E5FFAEA61186BB6AD436732FCCC25291A55895464CF6069CE26BF03466228F19A3A62DB8A649F2D560FAC652827D1AF0574E427AB63"),
should_fail_verify: false,
},
TestVector {
secret_key: hex_arr("0340034003400340034003400340034003400340034003400340034003400340"),
public_key: hex_arr("778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117"),
aux_rand: hex_arr("0000000000000000000000000000000000000000000000000000000000000000"),
message: hex_vec("11"),
signature: hex_arr("08A20A0AFEF64124649232E0693C583AB1B9934AE63B4C3511F3AE1134C6A303EA3173BFEA6683BD101FA5AA5DBC1996FE7CACFC5A577D33EC14564CEC2BACBF"),
should_fail_verify: false,
},
TestVector {
secret_key: hex_arr("0340034003400340034003400340034003400340034003400340034003400340"),
public_key: hex_arr("778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117"),
aux_rand: hex_arr("0000000000000000000000000000000000000000000000000000000000000000"),
message: hex_vec("0102030405060708090A0B0C0D0E0F1011"),
signature: hex_arr("5130F39A4059B43BC7CAC09A19ECE52B5D8699D1A71E3C52DA9AFDB6B50AC370C4A482B77BF960F8681540E25B6771ECE1E5A37FD80E5A51897C5566A97EA5A5"),
should_fail_verify: false,
},
TestVector {
secret_key: hex_arr("0340034003400340034003400340034003400340034003400340034003400340"),
public_key: hex_arr("778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117"),
aux_rand: hex_arr("0000000000000000000000000000000000000000000000000000000000000000"),
message: hex_vec("99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"),
signature: hex_arr("403B12B0D8555A344175EA7EC746566303321E5DBFA8BE6F091635163ECA79A8585ED3E3170807E7C03B720FC54C7B23897FCBA0E9D0B4A06894CFD249F22367"),
should_fail_verify: false,
},
];
let secp = Secp256k1::new();

for TestVector {
secret_key,
public_key,
aux_rand,
message,
signature,
should_fail_verify,
} in vectors
{
if let (Some(secret_key), Some(aux_rand)) = (secret_key, aux_rand) {
let keypair = Keypair::from_seckey_slice(&secp, &secret_key).unwrap();
assert_eq!(keypair.x_only_public_key().0.serialize(), public_key);
let sig = secp.sign_schnorr_with_aux_rand(&message, &keypair, &aux_rand);
assert_eq!(sig.serialize(), signature);
}
let sig = Signature::from_slice(&signature).unwrap();
let is_verified = if let Ok(pubkey) = XOnlyPublicKey::from_slice(&public_key) {
secp.verify_schnorr(&sig, &message, &pubkey).is_ok()
} else {
false
};
assert_eq!(is_verified, !should_fail_verify);
}
}
}
Loading