Skip to content

noir-lang/noir_string_search

Repository files navigation

noir_string_search

A noir library that can be used to prove that a given "needle" substring is present within a larger "haystack" string.

Features:

  • Both the needle and haystack string can have variable runtime lengths (up to a defined maximum)
  • Efficient implementation ~2 gates per haystack byte, ~5 gates per needle byte
  • Both needle and haystack strings can be dynamically constructed in-circuit if required

Typedefs

Multiple type definitions represent different hardcoded maximum lengths for the needle/haystack:

Haystack types ranging from 32 max bytes to 16,384 max bytes
StringBody32
StringBody64
StringBody128
StringBody256
StringBody512
StringBody1024
StringBody2048
StringBody4096
StringBody8192
StringBody16384
Needle types ranging from 32 bytes to 1,024 max bytes
SubString32
SubString64
SubString128
SubString256
SubString512
SubString1024

Usage

Basic usage

let haystack_text = "the quick brown fox jumped over the lazy dog".as_bytes();
let needle_text = " the lazy dog".as_bytes();

let haystack: StringBody64 = StringBody::new(haystack_text, haystack_text.len());
let needle: SubString32 = SubString::new(needle_text, needle_text.len());

let (result, match_position): (bool, u32) = haystack.substring_match(needle);

Dynamic needle construction

fn validate_account(padded_email_text: [u8; 8192], padded_username: [u8; 100], username_length: u32) {

    let needle_text_init = "account recovery for".as_bytes();

    let needle_start: SubString32 = SubString::new(needle_text_init, needle_text_init.len());
    let needle_end: SubString128 = SubString::new(padded_username, username_length);
    // use concat_into because SubString128 > SubString32
    let needle = needle_start.concat_into(needle_end);

    let haystack: StringBody8192 = StringBody::new(padded_email_text, 8192);
    let (result, match_position): (bool, u32) = haystack.substring_match(needle);
}

Costs

Matching a SubString128 with a StringBody1024 costs 6,630 gates (as of noir 0.32.0 and bb 0.46.1)

Gate breakdown:

  • 2,740 gates: constructing a 14-bit range table
  • 1,280 gates: constructing a 1,024 size u8 array
  • 160 gates: constructing a 128 size u8 array
  • 2,444 gates: string matching algorithm

Some rough measurements:

Haystack Bytes Needle Bytes Total Cost Cost minus range table and byte array init costs
1,024 128 6,630 2,444
1,024 256 7,633 3,293
2,048 128 8,471 3,011
2,048 256 9,474 3,854

Extrapolating from this table for some very rough estimates: costs for substring_match (excluding range table and byte array init costs) are ~6.5 gates per needle byte, ~0.5 gates per haystack byte and a ~1,100 gate constant cost.