Skip to content

Commit

Permalink
feat: BIP-340 Schnorr Signature (#334)
Browse files Browse the repository at this point in the history
<!--- Please provide a general summary of your changes in the title
above -->

BIP-340 has been implemented in Cairo by the JoyBoy community, and it
would be a good addition to add it to Alexandria.

## Pull Request type

<!-- Please try to limit your pull request to one type; submit multiple
pull requests if needed. -->

Please check the type of change your PR introduces:

- [ ] Bugfix
- [x] Feature
- [ ] Code style update (formatting, renaming)
- [ ] Refactoring (no functional changes, no API changes)
- [ ] Build-related changes
- [ ] Documentation content changes
- [ ] Other (please describe):

## What is the current behavior?

<!-- Please describe the current behavior that you are modifying, or
link to a relevant issue. -->

Issue Number: N/A

## What is the new behavior?

<!-- Please describe the behavior or changes that are being added by
this PR. -->

-
-
-

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this does introduce a breaking change, please describe the
impact and migration path for existing applications below. -->

## Other information

<!-- Any other information that is important to this PR, such as
screenshots of how the component looks before and after the change. -->
  • Loading branch information
egeaybars123 authored Oct 14, 2024
1 parent cdaa20d commit 33d56f2
Show file tree
Hide file tree
Showing 5 changed files with 352 additions and 0 deletions.
3 changes: 3 additions & 0 deletions packages/math/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ The perfect number algorithm is used to determine whether a given positive integ
A perfect number is a positive integer that is equal to the sum of its proper divisors (excluding itself).
The purpose of the algorithm is to identify these special numbers and to study their properties. Perfect numbers have applications in various areas of mathematics, including number theory and algebraic geometry. They are also used in cryptography and coding theory. The perfect number algorithm is important for understanding the structure of numbers and their relationships to each other, and it has been studied for centuries by mathematicians.

## [Schnorr Signature Verification - BIP340](./src/schnorr.cairo)
Schnorr Signature Verification has been implemented according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). Secp256k1 elliptic curve has been utilized, and Schnorr signature offers provable security, non-malleability (unlike ECDSA), and linearity which makes it efficient to aggregate signatures from multiple public keys.

## [Zeller's congruence](./src/zellers_congruence.cairo)

Zeller's congruence algorithm is used to determine the day of the week for a given date.
Expand Down
112 changes: 112 additions & 0 deletions packages/math/src/bip340.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//! bip340 implementation
use core::byte_array::ByteArrayTrait;
use core::math::u256_mul_mod_n;
use core::option::OptionTrait;
use core::result::ResultTrait;
use core::sha256::compute_sha256_byte_array; //Available in Cairo ^2.7.0.
use core::starknet::SyscallResultTrait;
use core::to_byte_array::{AppendFormattedToByteArray, FormatAsByteArray};
use core::traits::Into;

use starknet::{secp256k1::{Secp256k1Point}, secp256_trait::{Secp256Trait, Secp256PointTrait}};

const TWO_POW_32: u128 = 0x100000000;
const TWO_POW_64: u128 = 0x10000000000000000;
const TWO_POW_96: u128 = 0x1000000000000000000000000;

const p: u256 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F;


/// Computes BIP0340/challenge tagged hash.
///
/// References:
/// Schnorr signatures explained:
/// https://www.youtube.com/watch?v=wjACBRJDfxc&ab_channel=Bitcoinology
/// NIP-01:
/// https://github.com/nostr-protocol/nips/blob/master/01.md
/// BIP-340:
/// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
/// reference implementation:
/// https://github.com/bitcoin/bips/blob/master/bip-0340/reference.py
///
///
/// # Arguments:
/// * `rx`: `u256` - The x-coordinate of the R point from the signature.
/// * `px`: `u256` - The x-coordinate of the public key.
/// * `m`: `ByteArray` - The message for which the signature is being verified.
///
/// # Returns:
/// * `u256` - `sha256(tag) || sha256(tag) || bytes(rx) || bytes(px) || m` as u256 where tag =
/// "BIP0340/challenge".
fn hash_challenge(rx: u256, px: u256, m: ByteArray) -> u256 {
// sha256(tag)

//Precomputed values -> tag = 'compute_sha256_byte_array(@"BIP0340/challenge")'
// sha256(tag) || sha256(tag)
let mut ba: ByteArray = Default::default();
ba.append_word(0x7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d3, 31);
ba.append_word(0x7c7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48, 31);
ba.append_word(0xd37c, 2);
// bytes(rx)
ba.append_word(rx.high.into(), 16);
ba.append_word(rx.low.into(), 16);
// bytes(px)
ba.append_word(px.high.into(), 16);
ba.append_word(px.low.into(), 16);
// m
ba.append(@m);

let [x0, x1, x2, x3, x4, x5, x6, x7] = compute_sha256_byte_array(@ba);

u256 {
high: x0.into() * TWO_POW_96 + x1.into() * TWO_POW_64 + x2.into() * TWO_POW_32 + x3.into(),
low: x4.into() * TWO_POW_96 + x5.into() * TWO_POW_64 + x6.into() * TWO_POW_32 + x7.into(),
}
}

/// Verifies a signature according to the BIP-340.
///
/// This function checks if the signature `(rx, s)` is valid for a message `m` with
/// respect to the public key `px`.
///
/// # Arguments
/// * `px`: `u256` - The x-coordinate of the public key.
/// * `rx`: `u256` - The x-coordinate of the R point from the signature.
/// * `s`: `u256` - The scalar component of the signature.
/// * `m`: `ByteArray` - The message for which the signature is being verified.
///
/// # Returns
/// * `bool` - `true` if the signature is verified for the message and public key, `false` otherwise.
pub fn verify(px: u256, rx: u256, s: u256, m: ByteArray) -> bool {
let n = Secp256Trait::<Secp256k1Point>::get_curve_size();

if px >= p || rx >= p || s >= n {
return false;
}

// p - field size, n - curve order
// point P for which x(P) = px and has_even_y(P),
let P =
match Secp256Trait::<Secp256k1Point>::secp256_ec_get_point_from_x_syscall(px, false)
.unwrap_syscall() {
Option::Some(P) => P,
Option::None => { return false; }
};

// e = int(hashBIP0340/challenge(bytes(rx) || bytes(px) || m)) mod n.
let e = hash_challenge(rx, px, m) % n;

let G = Secp256Trait::<Secp256k1Point>::get_generator_point();

// R = s⋅G - e⋅P
let p1 = G.mul(s).unwrap_syscall();
let minus_e = Secp256Trait::<Secp256k1Point>::get_curve_size() - e;
let p2 = P.mul(minus_e).unwrap_syscall();

let R = p1.add(p2).unwrap_syscall();

let (Rx, Ry) = R.get_coordinates().unwrap_syscall();

// fail if is_infinite(R) || not has_even_y(R) || x(R) ≠ rx.
!(Rx == 0 && Ry == 0) && Ry % 2 == 0 && Rx == rx
}
1 change: 1 addition & 0 deletions packages/math/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod aliquot_sum;
pub mod armstrong_number;
pub mod bip340;
pub mod bitmap;
pub mod collatz_sequence;
pub mod ed25519;
Expand Down
1 change: 1 addition & 0 deletions packages/math/src/tests.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod aliquot_sum_test;
mod armstrong_number_test;
mod bip340_test;
mod bitmap_test;
mod collatz_sequence_test;
mod ed25519_test;
Expand Down
235 changes: 235 additions & 0 deletions packages/math/src/tests/bip340_test.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
use alexandria_math::bip340::{verify};
use core::byte_array::ByteArrayTrait;
use core::clone::Clone;
use core::option::OptionTrait;
use core::traits::Into;

impl U256IntoByteArray of Into<u256, ByteArray> {
fn into(self: u256) -> ByteArray {
let mut ba = Default::default();
ba.append_word(self.high.into(), 16);
ba.append_word(self.low.into(), 16);
ba
}
}

// test data adapted from: https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv

#[test]
fn test_0() {
let px: u256 = 0xf9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9;
let rx: u256 = 0xe907831f80848d1069a5371b402410364bdf1c5f8307b0084c55f1ce2dca8215;
let s: u256 = 0x25f66a4a85ea8b71e482a74f382d2ce5ebeee8fdb2172f477df4900d310536c0;
let m: u256 = 0x0;
assert!(verify(px, rx, s, m.into()));
}

#[test]
fn test_1() {
let px: u256 = 0xdff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659;
let rx: u256 = 0x6896bd60eeae296db48a229ff71dfe071bde413e6d43f917dc8dcf8c78de3341;
let s: u256 = 0x8906d11ac976abccb20b091292bff4ea897efcb639ea871cfa95f6de339e4b0a;
let m: u256 = 0x243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89;
assert!(verify(px, rx, s, m.into()));
}

#[test]
fn test_2() {
let px: u256 = 0xdd308afec5777e13121fa72b9cc1b7cc0139715309b086c960e18fd969774eb8;
let rx: u256 = 0x5831aaeed7b44bb74e5eab94ba9d4294c49bcf2a60728d8b4c200f50dd313c1b;
let s: u256 = 0xab745879a5ad954a72c45a91c3a51d3c7adea98d82f8481e0e1e03674a6f3fb7;
let m: u256 = 0x7e2d58d8b3bcdf1abadec7829054f90dda9805aab56c77333024b9d0a508b75c;

assert!(verify(px, rx, s, m.into()));
}

#[test]
fn test_3() {
let px: u256 = 0x25d1dff95105f5253c4022f628a996ad3a0d95fbf21d468a1b33f8c160d8f517;
let rx: u256 = 0x7eb0509757e246f19449885651611cb965ecc1a187dd51b64fda1edc9637d5ec;
let s: u256 = 0x97582b9cb13db3933705b32ba982af5af25fd78881ebb32771fc5922efc66ea3;
let m: u256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;

assert!(verify(px, rx, s, m.into()));
}

#[test]
fn test_4() {
let px: u256 = 0xd69c3509bb99e412e68b0fe8544e72837dfa30746d8be2aa65975f29d22dc7b9;
let rx: u256 = 0x3b78ce563f89a0ed9414f5aa28ad0d96d6795f9c63;
let s: u256 = 0x76afb1548af603b3eb45c9f8207dee1060cb71c04e80f593060b07d28308d7f4;
let m: u256 = 0x4df3c3f68fcc83b27e9d42c90431a72499f17875c81a599b566c9889b9696703;

assert!(verify(px, rx, s, m.into()));
}

#[test]
fn test_5() {
// public key not on the curve
let px: u256 = 0xeefdea4cdb677750a420fee807eacf21eb9898ae79b9768766e4faa04a2d4a34;
let rx: u256 = 0x6cff5c3ba86c69ea4b7376f31a9bcb4f74c1976089b2d9963da2e5543e177769;
let s: u256 = 0x69e89b4c5564d00349106b8497785dd7d1d713a8ae82b32fa79d5f7fc407d39b;
let m: u256 = 0x243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89;
assert_eq!(verify(px, rx, s, m.into()), false);
}

#[test]
fn test_6() {
// has_even_y(R) is false
let px: u256 = 0xdff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659;
let rx: u256 = 0xfff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556;
let s: u256 = 0x3cc27944640ac607cd107ae10923d9ef7a73c643e166be5ebeafa34b1ac553e2;
let m: u256 = 0x243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89;

assert_eq!(verify(px, rx, s, m.into()), false);
}

#[test]
fn test_7() {
// negated message
let px: u256 = 0xdff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659;
let rx: u256 = 0x1fa62e331edbc21c394792d2ab1100a7b432b013df3f6ff4f99fcb33e0e1515f;
let s: u256 = 0x28890b3edb6e7189b630448b515ce4f8622a954cfe545735aaea5134fccdb2bd;
let m: u256 = 0x243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89;

assert_eq!(verify(px, rx, s, m.into()), false);
}

#[test]
fn test_8() {
// negated s value
let px: u256 = 0xdff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659;
let rx: u256 = 0x6cff5c3ba86c69ea4b7376f31a9bcb4f74c1976089b2d9963da2e5543e177769;
let s: u256 = 0x961764b3aa9b2ffcb6ef947b6887a226e8d7c93e00c5ed0c1834ff0d0c2e6da6;
let m: u256 = 0x243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89;

assert_eq!(verify(px, rx, s, m.into()), false);
}

#[test]
fn test_9() {
// sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as
// true and x(inf) as 0
let px: u256 = 0xdff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659;
let rx: u256 = 0x0;
let s: u256 = 0x123dda8328af9c23a94c1feecfd123ba4fb73476f0d594dcb65c6425bd186051;
let m: u256 = 0x243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89;

assert_eq!(verify(px, rx, s, m.into()), false);
}

#[test]
fn test_10() {
// sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as
// true and x(inf) as 1
let px: u256 = 0xdff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659;
let rx: u256 = 0x1;
let s: u256 = 0x7615fbaf5ae28864013c099742deadb4dba87f11ac6754f93780d5a1837cf197;
let m: u256 = 0x243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89;

assert_eq!(verify(px, rx, s, m.into()), false);
}

#[test]
fn test_11() {
// sig[0:32] is not an X coordinate on the curve
let px: u256 = 0xdff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659;
let rx: u256 = 0x4a298dacae57395a15d0795ddbfd1dcb564da82b0f269bc70a74f8220429ba1d;
let s: u256 = 0x69e89b4c5564d00349106b8497785dd7d1d713a8ae82b32fa79d5f7fc407d39b;
let m: u256 = 0x243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89;
assert_eq!(verify(px, rx, s, m.into()), false);
}

#[test]
fn test_12() {
// sig[0:32] is equal to field size
let px: u256 = 0xdff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659;
let rx: u256 = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f;
let s: u256 = 0x69e89b4c5564d00349106b8497785dd7d1d713a8ae82b32fa79d5f7fc407d39b;
let m: u256 = 0x243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89;
assert_eq!(verify(px, rx, s, m.into()), false);
}

#[test]
fn test_13() {
// sig[32:64] is equal to curve order
let px: u256 = 0xdff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659;
let rx: u256 = 0x6cff5c3ba86c69ea4b7376f31a9bcb4f74c1976089b2d9963da2e5543e177769;
let s: u256 = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
let m: u256 = 0x243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89;
assert_eq!(verify(px, rx, s, m.into()), false);
}

#[test]
fn test_14() {
// public key is not a valid X coordinate because it exceeds the field size
let px: u256 = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30;
let rx: u256 = 0x6cff5c3ba86c69ea4b7376f31a9bcb4f74c1976089b2d9963da2e5543e177769;
let s: u256 = 0x69e89b4c5564d00349106b8497785dd7d1d713a8ae82b32fa79d5f7fc407d39b;
let m: u256 = 0x243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89;
assert_eq!(verify(px, rx, s, m.into()), false);
}

#[test]
fn test_15() {
// message of size 0
let px: u256 = 0x778caa53b4393ac467774d09497a87224bf9fab6f6e68b23086497324d6fd117;
let rx: u256 = 0x71535db165ecd9fbbc046e5ffaea61186bb6ad436732fccc25291a55895464cf;
let s: u256 = 0x6069ce26bf03466228f19a3a62db8a649f2d560fac652827d1af0574e427ab63;
let m = "";
assert!(verify(px, rx, s, m));
}

#[test]
fn test_16() {
// message of size 1
let px: u256 = 0x778caa53b4393ac467774d09497a87224bf9fab6f6e68b23086497324d6fd117;
let rx: u256 = 0x8a20a0afef64124649232e0693c583ab1b9934ae63b4c3511f3ae1134c6a303;
let s: u256 = 0xea3173bfea6683bd101fa5aa5dbc1996fe7cacfc5a577d33ec14564cec2bacbf;
let m = "\x11";
assert!(verify(px, rx, s, m));
}

#[test]
fn test_17() {
// message of size 17
let px: u256 = 0x778caa53b4393ac467774d09497a87224bf9fab6f6e68b23086497324d6fd117;
let rx: u256 = 0x5130f39a4059b43bc7cac09a19ece52b5d8699d1a71e3c52da9afdb6b50ac370;
let s: u256 = 0xc4a482b77bf960f8681540e25b6771ece1e5a37fd80e5a51897c5566a97ea5a5;
let m = "\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11";

assert!(verify(px, rx, s, m));
}

#[test]
fn test_18() {
// message of size 100
let px: u256 = 0x778caa53b4393ac467774d09497a87224bf9fab6f6e68b23086497324d6fd117;
let rx: u256 = 0x403b12b0d8555a344175ea7ec746566303321e5dbfa8be6f091635163eca79a8;
let s: u256 = 0x585ed3e3170807e7c03b720fc54c7b23897fcba0e9d0b4a06894cfd249f22367;

let mut m: ByteArray = Default::default();
let mut nines: ByteArray =
0x9999999999999999999999999999999999999999999999999999999999999999_u256
.into();
m.append(@nines);
m.append(@nines);
m.append(@nines);
m.append_byte(0x99);
m.append_byte(0x99);
m.append_byte(0x99);
m.append_byte(0x99);

assert!(verify(px, rx, s, m));
}

#[test]
fn test_19() {
// signature of message: joyboy, generated in browser with nos2x extension
let px: u256 = 0x98298b0b4a0d586771e7f84c742394b5013d37c16af0924bd7ee62ec6a517a5d;
let rx: u256 = 0x3b7a0877cefa952d536fc167446a22f017922743db5cddd912b7890b7c5c34fe;
let s: u256 = 0x2591fff0a4ac15d3ed5d3f767e686e771ec456af2fb53ffba163e509e16b0eba;
let m: u256 = 0x2e5673c8b39f7a0d41219676661159c59a93644c06b81684718b8a0cd53f7f06;

assert!(verify(px, rx, s, m.into()));
}

0 comments on commit 33d56f2

Please sign in to comment.