Skip to content

Commit 58b809c

Browse files
authored
Updated WOTS API (BitVM#293)
* chore: Mark potential footgun * feat: New WOTS API * test: Add WOTS test vectors The test vectors are generated directly in Rust, so we can add new vectors or modify existing vectors in the future. * refactor: Adapt bitvm crate to new WOTS API Removes the dependency on the modules wots_api and signing_winternitz. * refactor: adapt bridge to new wots api Removes the dependency on wots_api. Removing signing_winternitz turned out to be too much work, as it touches a lot of code. In particular, the connector outputs handle public keys for different message lengths in the same vector. The bridge code can be refactored in the future if needed. * refactor: Move winternitz_hash into signing_winternitz signing_winternitz is the only place that calls winternitz_hash, so why not combine the modules? BLAKE3 switched from 20-byte WOTS signatures to Wots16 a while ago, so I use the opportunity to remove the code for 20-byte WOTS. * chore: Remove wots_api.rs No more code depends on this, so it is safe to remove. * fix: Address comments * refactor: WinternitzSecret constructors Deprecate from_string constructors. Add from_bytes as a safer alternative. * fix: Address comments * fix: address comments * fix: cargo fmt
1 parent 663717c commit 58b809c

14 files changed

Lines changed: 6195 additions & 408 deletions

File tree

bitvm/src/chunk/api.rs

Lines changed: 33 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::chunk::api_runtime_utils::{
77
get_segments_from_assertion, get_segments_from_groth16_proof,
88
};
99

10-
use crate::signatures::wots_api::{wots256, wots_hash};
10+
use crate::signatures::{Wots, Wots16, Wots32};
1111
use crate::treepp::*;
1212
use ark_bn254::Bn254;
1313
use ark_ec::bn::Bn;
@@ -28,15 +28,15 @@ pub const NUM_TAPS: usize = HASHING_TAPS + VALIDATING_TAPS;
2828
pub type PublicInputs = [ark_bn254::Fr; NUM_PUBS];
2929

3030
pub type PublicKeys = (
31-
[wots256::PublicKey; NUM_PUBS],
32-
[wots256::PublicKey; NUM_U256],
33-
[wots_hash::PublicKey; NUM_HASH],
31+
[<Wots32 as Wots>::PublicKey; NUM_PUBS],
32+
[<Wots32 as Wots>::PublicKey; NUM_U256],
33+
[<Wots16 as Wots>::PublicKey; NUM_HASH],
3434
);
3535

3636
pub type Signatures = (
37-
Box<[wots256::Signature; NUM_PUBS]>,
38-
Box<[wots256::Signature; NUM_U256]>,
39-
Box<[wots_hash::Signature; NUM_HASH]>,
37+
Box<[<Wots32 as Wots>::Signature; NUM_PUBS]>,
38+
Box<[<Wots32 as Wots>::Signature; NUM_U256]>,
39+
Box<[<Wots16 as Wots>::Signature; NUM_HASH]>,
4040
);
4141

4242
pub type Assertions = (
@@ -54,14 +54,12 @@ pub fn api_get_assertions_from_signature(signed_asserts: Signatures) -> Assertio
5454
}
5555

5656
pub mod type_conversion_utils {
57+
use super::*;
5758
use crate::chunk::api::Signatures;
5859
use crate::{
5960
chunk::api::{NUM_HASH, NUM_PUBS, NUM_U256},
6061
execute_script,
61-
signatures::{
62-
signing_winternitz::WinternitzPublicKey,
63-
wots_api::{wots256, wots_hash},
64-
},
62+
signatures::GenericWinternitzPublicKey,
6563
treepp::Script,
6664
};
6765
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
@@ -93,18 +91,17 @@ pub mod type_conversion_utils {
9391
assert_eq!(raw_wits.len(), NUM_PUBS + NUM_U256 + NUM_HASH);
9492
let mut asigs = vec![];
9593
for i in 0..NUM_PUBS {
96-
let a = wots256::raw_witness_to_signature(&Witness::from_slice(&raw_wits[i]));
94+
let a = Wots32::raw_witness_to_signature(&Witness::from_slice(&raw_wits[i]));
9795
asigs.push(a);
9896
}
9997
let mut bsigs = vec![];
10098
for i in 0..NUM_U256 {
101-
let a =
102-
wots256::raw_witness_to_signature(&Witness::from_slice(&raw_wits[i + NUM_PUBS]));
99+
let a = Wots32::raw_witness_to_signature(&Witness::from_slice(&raw_wits[i + NUM_PUBS]));
103100
bsigs.push(a);
104101
}
105102
let mut csigs = vec![];
106103
for i in 0..NUM_HASH {
107-
let a = wots_hash::raw_witness_to_signature(&Witness::from_slice(
104+
let a = Wots16::raw_witness_to_signature(&Witness::from_slice(
108105
&raw_wits[i + NUM_PUBS + NUM_U256],
109106
));
110107
csigs.push(a);
@@ -124,13 +121,13 @@ pub mod type_conversion_utils {
124121
let mut raw_wits = Vec::with_capacity(asigs.len() + bsigs.len() + csigs.len());
125122

126123
for sig in asigs.iter() {
127-
raw_wits.push(wots256::signature_to_raw_witness(sig).to_vec());
124+
raw_wits.push(Wots32::signature_to_raw_witness(sig).to_vec());
128125
}
129126
for sig in bsigs.iter() {
130-
raw_wits.push(wots256::signature_to_raw_witness(sig).to_vec());
127+
raw_wits.push(Wots32::signature_to_raw_witness(sig).to_vec());
131128
}
132129
for sig in csigs.iter() {
133-
raw_wits.push(wots_hash::signature_to_raw_witness(sig).to_vec());
130+
raw_wits.push(Wots16::signature_to_raw_witness(sig).to_vec());
134131
}
135132

136133
raw_wits
@@ -146,20 +143,20 @@ pub mod type_conversion_utils {
146143
}
147144

148145
pub fn utils_typed_pubkey_from_raw(
149-
commits_public_keys: Vec<&WinternitzPublicKey>,
146+
commits_public_keys: Vec<&GenericWinternitzPublicKey>,
150147
) -> PublicKeys {
151148
let mut apubs = vec![];
152149
let mut bpubs = vec![];
153150
let mut cpubs = vec![];
154151
for (idx, f) in commits_public_keys.into_iter().enumerate() {
155152
if idx < NUM_PUBS {
156-
let p: wots256::PublicKey = f.public_key.clone().try_into().unwrap();
153+
let p: <Wots32 as Wots>::PublicKey = f.clone().try_into().unwrap();
157154
apubs.push(p);
158155
} else if idx < NUM_PUBS + NUM_U256 {
159-
let p: wots256::PublicKey = f.public_key.clone().try_into().unwrap();
156+
let p: <Wots32 as Wots>::PublicKey = f.clone().try_into().unwrap();
160157
bpubs.push(p);
161158
} else if idx < NUM_PUBS + NUM_U256 + NUM_HASH {
162-
let p: wots_hash::PublicKey = f.public_key.clone().try_into().unwrap();
159+
let p: <Wots16 as Wots>::PublicKey = f.clone().try_into().unwrap();
163160
cpubs.push(p);
164161
}
165162
}
@@ -356,8 +353,6 @@ mod test {
356353
use crate::chunk::api::generate_signatures_for_any_proof;
357354

358355
use crate::chunk::wrap_hasher::BLAKE3_HASH_LENGTH;
359-
use crate::chunk::wrap_wots::{byte_array_to_wots256_sig, byte_array_to_wots_hash_sig};
360-
use crate::signatures::wots_api::{wots256, wots_hash};
361356
use ark_bn254::Bn254;
362357
use ark_ff::UniformRand;
363358
use ark_serialize::CanonicalDeserialize;
@@ -369,6 +364,8 @@ mod test {
369364
write_scripts_to_file, write_scripts_to_separate_files,
370365
};
371366

367+
use super::Signatures;
368+
use crate::signatures::{Wots, Wots16, Wots32};
372369
use crate::{
373370
chunk::{
374371
api::{
@@ -383,8 +380,6 @@ mod test {
383380
execute_script,
384381
};
385382

386-
use super::Signatures;
387-
388383
mod test_utils {
389384
use crate::chunk::api::Assertions;
390385
use crate::chunk::api::NUM_HASH;
@@ -768,29 +763,29 @@ mod test {
768763
let (ps, fs, hs) = (assn.0, assn.1, assn.2);
769764
let secret = MOCK_SECRET;
770765

771-
let mut psig: Vec<wots256::Signature> = vec![];
766+
let mut psig: Vec<<Wots32 as Wots>::Signature> = vec![];
772767
for i in 0..NUM_PUBS {
773-
let psi = byte_array_to_wots256_sig(&format!("{secret}{:04x}", i), &ps[i]);
768+
let secret = format!("{secret}{:04x}", i);
769+
let psi = Wots32::sign(&Wots16::secret_from_str(&secret), &ps[i]);
774770
psig.push(psi);
775771
}
776-
let psig: Box<[wots256::Signature; NUM_PUBS]> = Box::new(psig.try_into().unwrap());
772+
let psig: Box<[<Wots32 as Wots>::Signature; NUM_PUBS]> = Box::new(psig.try_into().unwrap());
777773

778-
let mut fsig: Vec<wots256::Signature> = vec![];
774+
let mut fsig: Vec<<Wots32 as Wots>::Signature> = vec![];
779775
for i in 0..NUM_U256 {
780-
let fsi = byte_array_to_wots256_sig(&format!("{secret}{:04x}", NUM_PUBS + i), &fs[i]);
776+
let secret = format!("{secret}{:04x}", NUM_PUBS + i);
777+
let fsi = Wots32::sign(&Wots16::secret_from_str(&secret), &fs[i]);
781778
fsig.push(fsi);
782779
}
783-
let fsig: Box<[wots256::Signature; NUM_U256]> = Box::new(fsig.try_into().unwrap());
780+
let fsig: Box<[<Wots32 as Wots>::Signature; NUM_U256]> = Box::new(fsig.try_into().unwrap());
784781

785-
let mut hsig: Vec<wots_hash::Signature> = vec![];
782+
let mut hsig: Vec<<Wots16 as Wots>::Signature> = vec![];
786783
for i in 0..NUM_HASH {
787-
let hsi = byte_array_to_wots_hash_sig(
788-
&format!("{secret}{:04x}", NUM_PUBS + NUM_U256 + i),
789-
&hs[i],
790-
);
784+
let secret = format!("{secret}{:04x}", NUM_PUBS + NUM_U256 + i);
785+
let hsi = Wots16::sign(&Wots16::secret_from_str(&secret), &hs[i]);
791786
hsig.push(hsi);
792787
}
793-
let hsig: Box<[wots_hash::Signature; NUM_HASH]> = Box::new(hsig.try_into().unwrap());
788+
let hsig: Box<[<Wots16 as Wots>::Signature; NUM_HASH]> = Box::new(hsig.try_into().unwrap());
794789

795790
(psig, fsig, hsig)
796791
}

bitvm/src/chunk/api_runtime_utils.rs

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use crate::chunk::g16_runner_core::InputProof;
1111
use crate::chunk::g16_runner_core::InputProofRaw;
1212
use crate::chunk::g16_runner_core::PublicParams;
1313
use crate::groth16::offchain_checker::compute_c_wi;
14-
use crate::signatures::wots_api::{wots256, wots_hash, SignatureImpl};
1514
use crate::treepp::Script;
1615
use ark_bn254::Bn254;
1716
use ark_ec::bn::Bn;
@@ -20,21 +19,18 @@ use ark_ff::Field;
2019
use bitcoin::ScriptBuf;
2120
use bitcoin_script::script;
2221

23-
use crate::{bn254::utils::Hint, execute_script};
24-
2522
use super::api::{Assertions, PublicKeys, Signatures, NUM_HASH, NUM_PUBS, NUM_TAPS, NUM_U256};
23+
use super::elements::CompressedStateObject;
2624
use super::g16_runner_utils::{ScriptType, Segment};
2725
use super::wrap_hasher::BLAKE3_HASH_LENGTH;
28-
use super::{
29-
elements::CompressedStateObject,
30-
wrap_wots::{wots256_sig_to_byte_array, wots_hash_sig_to_byte_array},
31-
};
26+
use crate::signatures::{CompactWots, Wots, Wots16, Wots32};
27+
use crate::{bn254::utils::Hint, execute_script};
3228

3329
#[derive(Debug, Clone)]
3430
#[allow(clippy::large_enum_variant)]
3531
enum SigData {
36-
Sig256(wots256::Signature),
37-
SigHash(wots_hash::Signature),
32+
Wots16(<Wots16 as Wots>::Signature),
33+
Wots32(<Wots32 as Wots>::Signature),
3834
}
3935

4036
// Segments are collected in the order [PublicInputSegment, ProofInputSegments, IntermediateHashSegments, FinalScriptSegment]
@@ -304,26 +300,29 @@ pub(crate) fn get_signature_from_assertion(assn: Assertions, secrets: Vec<String
304300
// sign and return Signatures
305301
let (ps, fs, hs) = (assn.0, assn.1, assn.2);
306302

307-
let mut psig: Vec<wots256::Signature> = vec![];
303+
let mut psig: Vec<<Wots32 as Wots>::Signature> = vec![];
308304
for i in 0..NUM_PUBS {
309-
let psi = wots256::get_signature(secrets[i].as_str(), &ps[i]);
305+
let secret = Wots32::secret_from_str(secrets[i].as_str());
306+
let psi = Wots32::sign(&secret, &ps[i]);
310307
psig.push(psi);
311308
}
312-
let psig: Box<[wots256::Signature; NUM_PUBS]> = Box::new(psig.try_into().unwrap());
309+
let psig: Box<[<Wots32 as Wots>::Signature; NUM_PUBS]> = Box::new(psig.try_into().unwrap());
313310

314-
let mut fsig: Vec<wots256::Signature> = vec![];
311+
let mut fsig: Vec<<Wots32 as Wots>::Signature> = vec![];
315312
for i in 0..fs.len() {
316-
let fsi = wots256::get_signature(secrets[i + NUM_PUBS].as_str(), &fs[i]);
313+
let secret = Wots32::secret_from_str(secrets[i + NUM_PUBS].as_str());
314+
let fsi = Wots32::sign(&secret, &fs[i]);
317315
fsig.push(fsi);
318316
}
319-
let fsig: Box<[wots256::Signature; NUM_U256]> = Box::new(fsig.try_into().unwrap());
317+
let fsig: Box<[<Wots32 as Wots>::Signature; NUM_U256]> = Box::new(fsig.try_into().unwrap());
320318

321-
let mut hsig: Vec<wots_hash::Signature> = vec![];
319+
let mut hsig: Vec<<Wots16 as Wots>::Signature> = vec![];
322320
for i in 0..hs.len() {
323-
let hsi = wots_hash::get_signature(secrets[i + NUM_PUBS + NUM_U256].as_str(), &hs[i]);
321+
let secret = Wots16::secret_from_str(secrets[i + NUM_PUBS + NUM_U256].as_str());
322+
let hsi = Wots16::sign(&secret, &hs[i]);
324323
hsig.push(hsi);
325324
}
326-
let hsig: Box<[wots_hash::Signature; NUM_HASH]> = Box::new(hsig.try_into().unwrap());
325+
let hsig: Box<[<Wots16 as Wots>::Signature; NUM_HASH]> = Box::new(hsig.try_into().unwrap());
327326

328327
(psig, fsig, hsig)
329328
}
@@ -334,24 +333,21 @@ pub(crate) fn get_assertions_from_signature(signed_asserts: Signatures) -> Asser
334333
println!("get_assertions_from_signature");
335334
let mut ks: Vec<[u8; 32]> = vec![];
336335
for i in 0..NUM_PUBS {
337-
let nibs = wots256_sig_to_byte_array(signed_asserts.0[i]);
338-
let nibs: [u8; 32] = nibs.try_into().unwrap();
336+
let nibs = Wots32::signature_to_message(&signed_asserts.0[i]);
339337
ks.push(nibs);
340338
}
341339
let ks: [[u8; 32]; NUM_PUBS] = ks.try_into().unwrap();
342340

343341
let mut numfqs: Vec<[u8; 32]> = vec![];
344342
for i in 0..NUM_U256 {
345-
let nibs = wots256_sig_to_byte_array(signed_asserts.1[i]);
346-
let nibs: [u8; 32] = nibs.try_into().unwrap();
343+
let nibs = Wots32::signature_to_message(&signed_asserts.1[i]);
347344
numfqs.push(nibs);
348345
}
349346
let num_fqs: [[u8; 32]; NUM_U256] = numfqs.try_into().unwrap();
350347

351348
let mut numhashes: Vec<[u8; BLAKE3_HASH_LENGTH]> = vec![];
352349
for i in 0..NUM_HASH {
353-
let nibs = wots_hash_sig_to_byte_array(signed_asserts.2[i]);
354-
let nibs: [u8; BLAKE3_HASH_LENGTH] = nibs.try_into().unwrap();
350+
let nibs = Wots16::signature_to_message(&signed_asserts.2[i]);
355351
numhashes.push(nibs);
356352
}
357353

@@ -513,17 +509,17 @@ pub(crate) fn execute_script_from_signature(
513509
let scalar_sigs: Vec<SigData> = signed_asserts
514510
.0
515511
.iter()
516-
.map(|f| SigData::Sig256(*f))
512+
.map(|f| SigData::Wots32(*f))
517513
.collect();
518514
let felts_sigs: Vec<SigData> = signed_asserts
519515
.1
520516
.iter()
521-
.map(|f| SigData::Sig256(*f))
517+
.map(|f| SigData::Wots32(*f))
522518
.collect();
523519
let hash_sigs: Vec<SigData> = signed_asserts
524520
.2
525521
.iter()
526-
.map(|f| SigData::SigHash(*f))
522+
.map(|f| SigData::Wots16(*f))
527523
.collect();
528524
let mut bitcom_sig_arr = vec![];
529525
bitcom_sig_arr.extend_from_slice(&scalar_sigs);
@@ -552,8 +548,12 @@ pub(crate) fn execute_script_from_signature(
552548
for index in index_of_bitcommitted_msg {
553549
let sig_data = &bitcom_sig_arr[index as usize];
554550
let sig_preimage = match sig_data {
555-
SigData::SigHash(signature) => signature.to_compact_script(),
556-
SigData::Sig256(signature) => signature.to_compact_script(),
551+
SigData::Wots16(signature) => Wots16::compact_signature_to_raw_witness(
552+
&Wots16::signature_to_compact_signature(signature),
553+
),
554+
SigData::Wots32(signature) => Wots32::compact_signature_to_raw_witness(
555+
&Wots32::signature_to_compact_signature(signature),
556+
),
557557
};
558558
sig_preimages = script! {
559559
{sig_preimages}
@@ -577,16 +577,19 @@ pub(crate) fn execute_script_from_signature(
577577
pub(crate) fn get_pubkeys(secret_key: Vec<String>) -> PublicKeys {
578578
let mut pubins = vec![];
579579
for i in 0..NUM_PUBS {
580-
pubins.push(wots256::generate_public_key(secret_key[i].as_str()));
580+
let secret = Wots32::secret_from_str(secret_key[i].as_str());
581+
pubins.push(Wots32::generate_public_key(&secret));
581582
}
582583
let mut fq_arr = vec![];
583584
for i in 0..NUM_U256 {
584-
let p256 = wots256::generate_public_key(secret_key[i + NUM_PUBS].as_str());
585+
let secret = Wots32::secret_from_str(secret_key[i + NUM_PUBS].as_str());
586+
let p256 = Wots32::generate_public_key(&secret);
585587
fq_arr.push(p256);
586588
}
587589
let mut h_arr = vec![];
588590
for i in 0..NUM_HASH {
589-
let phash = wots_hash::generate_public_key(secret_key[i + NUM_PUBS + NUM_U256].as_str());
591+
let secret = Wots16::secret_from_str(secret_key[i + NUM_PUBS + NUM_U256].as_str());
592+
let phash = Wots16::generate_public_key(&secret);
590593
h_arr.push(phash);
591594
}
592595
let wotspubkey: PublicKeys = (

bitvm/src/chunk/wrap_hasher.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@ use super::elements::ElementType;
22
use crate::{
33
bn254::{fp254impl::Fp254Impl, fq::Fq},
44
hash::blake3::blake3_compute_script,
5-
signatures::wots_api,
65
treepp::*,
76
};
87
use hash_utils::{
98
hash_fp2, hash_fp6, hash_g2acc, hash_g2acc_with_hash_t, hash_g2acc_with_hashed_le,
109
};
1110

12-
pub const BLAKE3_HASH_LENGTH: usize = wots_api::HASH_LEN as usize;
11+
/// Number of bits that are used for WOTS signing.
12+
///
13+
/// The remaining bits are technically malleable,
14+
/// although collisions should be hard to find,
15+
/// as long as the number of malleable bits stays low.
16+
///
17+
/// Currently, 4 out of 20 bits are malleable.
18+
pub const BLAKE3_HASH_LENGTH: usize = 16;
1319

1420
/// truncate 32 byte output hash to {BLAKE3_HASH_LENGTH} hash output and pad with zeros
1521
fn wrap_scr(scr: Script) -> Script {

0 commit comments

Comments
 (0)