Skip to content

Commit

Permalink
Merge pull request #12 from ArnaudBrousseau/os2ip
Browse files Browse the repository at this point in the history
Bring back `os2ip`, and add concrete examples to README
  • Loading branch information
ArnaudBrousseau authored Sep 12, 2023
2 parents 6b52398 + c24b169 commit 5f4d930
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 13 deletions.
79 changes: 79 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,85 @@ In order of importance:
- **Compatibility with Ethereum**: BLS signatures produced by this crate should work with Ethereum (choice of variant explained [here](https://github.com/ethereum/consensus-specs/blob/v1.0.0/specs/phase0/beacon-chain.md#bls-signatures)).
- **Simplicity**: while the Domain-Separation-Tag isn't hardcoded (we need flexibility to test against multiple types of test vectors), we are hardcoding the choice of elliptic curve (BLS12-381), hash function (SHA-256), and variant (minimal-pubkey-size).

## Usage

### Basic Signature and Verification

```rust
use bls_on_arkworks as bls;
use rand_core::{RngCore, OsRng};

// We start with 64 bytes of good randomness from the OS.
// ikm has to be at least 32 bytes long to be secure, but can be longer.
let mut ikm = [0u8; 64];
OsRng.fill_bytes(&mut ikm);

// Build a secret key from the random bytes.
// The secret key is a field element.
let secret_key = bls::keygen(&ikm.to_vec());

// Sign a message with the Ethereum Domain Separation Tag
let message = "message to sign".as_bytes().to_vec();
let dst = bls::DST_ETHEREUM.as_bytes().to_vec();

let signature = bls::sign(secret_key, &message, &dst).unwrap();


// Derive a public key from our secret key above...
let public_key = bls::sk_to_pk(secret_key);
// ...and verify the signature we just produced.
let verified = bls::verify(&public_key, &message, &signature, &dst);
```

### Aggregates

This crate supports aggregate signatures and verification:

```rust
use bls_on_arkworks as bls;

// Load known hex bytes (instead of generating a new random secret key like in the previous example)
let sk1 = bls::os2ip(
&vec![
0x32, 0x83, 0x88, 0xaf, 0xf0, 0xd4, 0xa5, 0xb7,
0xdc, 0x92, 0x05, 0xab, 0xd3, 0x74, 0xe7, 0xe9,
0x8f, 0x3c, 0xd9, 0xf3, 0x41, 0x8e, 0xdb, 0x4e,
0xaf, 0xda, 0x5f, 0xb1, 0x64, 0x73, 0xd2, 0x16,
]
);
let sk2 = bls::os2ip(
&vec![
0x47, 0xb8, 0x19, 0x2d, 0x77, 0xbf, 0x87, 0x1b,
0x62, 0xe8, 0x78, 0x59, 0xd6, 0x53, 0x92, 0x27,
0x25, 0x72, 0x4a, 0x5c, 0x03, 0x1a, 0xfe, 0xab,
0xc6, 0x0b, 0xce, 0xf5, 0xff, 0x66, 0x51, 0x38,
]
);

// Sign a message with the Ethereum Domain Separation Tag
let dst = bls::DST_ETHEREUM.as_bytes().to_vec();
let message = "message to be signed by multiple parties".as_bytes().to_vec();

let first_signature = bls::sign(sk1, &message, &dst).unwrap();
let second_signature = bls::sign(sk2, &message, &dst).unwrap();

let aggregate = bls::aggregate(&vec![first_signature, second_signature]).unwrap();

// Derive a public key from our secret keys...
let pk1 = bls::sk_to_pk(sk1);
let pk2 = bls::sk_to_pk(sk2);
// ...and verify the aggregate signature we produced.
let verified = bls::aggregate_verify(
vec![pk1, pk2],
vec![message.clone(), message],
&aggregate,
&dst);
```

### Error handling

All errors are consolidated under a single `BLSError` enum. We favor `Result`-based interfaces over internal `panic`s.

## Testing

To run tests:
Expand Down
51 changes: 42 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,11 @@ pub fn signature_to_point(signature: &Signature) -> Result<G2AffinePoint, BLSErr
}

/// ([spec link](https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#section-2.3))
/// Generates a secret key SK deterministically from a secret octet string IKM
/// Generates a secret key SK deterministically from a secret octet string IKM.
/// IKM MUST be at least 32 bytes long, but it MAY be longer.
///
/// IKM should come from a good source of randomness, such as `rand::rngs::OsRng`.
/// If you want to load known secret key bytes instead of generating a new key, use [`os2ip`].
///
/// Implementation:
/// ```plain
Expand Down Expand Up @@ -205,13 +209,9 @@ pub fn keygen(ikm: &Octets) -> SecretKey {
hk.expand(&info, &mut okm).expect("unable to expand HKDF");

// 4
// XXX: deviation from the spec here; we don't call our own `OS2IP`.
// Arkworks' `from_be_bytes_mod_order` implements the same functionality as
// well as proper reduction modulo [`BLSFr::MODULUS`].
// To convince yourself, go read the relevant section of the spec:
// [here](https://datatracker.ietf.org/doc/html/rfc8017#section-4.2).
//
// `OS2IP` is essentially "deserialize integer from big-endian octets".
let sk = BLSFr::from_be_bytes_mod_order(&okm);
let sk = os2ip(&okm);

// 5
if !sk.is_zero() {
Expand All @@ -229,6 +229,8 @@ pub fn keygen(ikm: &Octets) -> SecretKey {
/// ([spec link](https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#section-2.4))
/// Takes a secret key SK and outputs the corresponding public key PK.
///
/// Use [`keygen`] to generate a brand new secret key, or [`os2ip`] to load known bytes.
///
/// Implementation:
/// ```plain
/// 1. xP = SK * P
Expand Down Expand Up @@ -514,9 +516,18 @@ fn subgroup_check_e2(p: G2AffinePoint) -> bool {
p.is_on_curve() && p.is_in_correct_subgroup_assuming_on_curve() && !p.is_zero()
}

/// ([spec link](https://www.rfc-editor.org/rfc/rfc8017.html#section-4.2))
/// Converts an octet string to a nonnegative integer.
/// This function loads bytes as a big-endian number and returns a valid [`SecretKey`] between 0 and p-1.
pub fn os2ip(octets: &Octets) -> SecretKey {
// Implements the spec functionality as well as proper reduction modulo [`BLSFr::MODULUS`].
SecretKey::from_be_bytes_mod_order(octets)
}

#[cfg(test)]
mod test {
use super::*;
use ark_ff::BigInteger;
use hex::ToHex;
use hex_literal::hex;
use num_bigint::BigInt;
Expand Down Expand Up @@ -734,9 +745,31 @@ mod test {
assert_ne!(p, q);
}

#[test]
fn test_os2ip_performs_reduction_modulo_p() {
// Obtained from https://github.com/arkworks-rs/curves/blob/8765798eb08d3dafc3f9362a12170ad0265ca6af/bls12_381/src/fields/fr.rs#L4
// then converted from decimal to hex using https://www.rapidtables.com/convert/number/decimal-to-hex.html
let field_modulus =
hex::decode("73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001")
.unwrap();
// Ensures that os2ip is performing proper reduction modulo p
assert!(os2ip(&field_modulus).is_zero());
}

#[test]
fn test_os2ip_parses_large_ints_correctly() {
// Subtract one from the field modulus (easy given the modulus ends in 0000001!) and compare the result of os2ip to hardcoded big ints from arkworks.
let field_modulus_minus_one =
hex::decode("73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000000")
.unwrap();
let mut expected = BLSFr::MODULUS;
expected.sub_with_borrow(&BigInteger256::one());

assert_eq!(os2ip(&field_modulus_minus_one).into_bigint(), expected,);
}

// Test helper to get a SecretKey (field element) from a hex-encoded string
fn hex_string_to_big_int(s: &str) -> SecretKey {
let bytes = hex::decode(s).unwrap();
SecretKey::from_be_bytes_mod_order(&bytes)
os2ip(&hex::decode(s).unwrap())
}
}
2 changes: 1 addition & 1 deletion src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub type Octets = Vec<u8>;

/// A secret key is an integer between 0 and the base field modulus
/// In other words: a field element.
/// To obtain a [`SecretKey`] from bytes, use `SecretKey::from_be_bytes_mod_order(&bytes)`.
/// To obtain a [`SecretKey`] from bytes, use [`super::os2ip`].
pub type SecretKey = BLSFr;

/// Represents a point in G1
Expand Down
8 changes: 5 additions & 3 deletions tests/cases.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use ark_ff::PrimeField;
use bls_on_arkworks::types::{Octets, SecretKey};
use bls_on_arkworks::{
os2ip,
types::{Octets, SecretKey},
};
use std::fs;

pub fn aggregate() -> Vec<AggregateCase> {
Expand Down Expand Up @@ -275,5 +277,5 @@ fn prefixed_hex_string_to_secret_key(s: &str) -> SecretKey {
non_prefixed.remove(0);

let bytes = hex::decode(non_prefixed).unwrap();
SecretKey::from_be_bytes_mod_order(&bytes)
os2ip(&bytes)
}

0 comments on commit 5f4d930

Please sign in to comment.