winterwallet-core 0.1.0

no_std Winternitz one-time signature primitives with BIP-39-style hierarchical key derivation, designed for efficient Solana on-chain verification.
Documentation
use winterwallet_core::{WinternitzKeypair, WinternitzPrivkey};

const MNEMONIC: &str =
    "earn foster affair make exclude object spring oppose one hollow garage kind";

#[test]
fn seed_from_mnemonic() {
    let seed = WinternitzKeypair::seed(MNEMONIC).expect("mnemonic should validate");
    assert_eq!(seed.len(), 64);
    assert_eq!(
        seed,
        [
            0xf0, 0xfe, 0xc3, 0xcb, 0x07, 0x99, 0xaf, 0xfe, 0xa8, 0x60, 0xa7, 0x5b, 0x1a, 0x27,
            0x0d, 0xe1, 0xbf, 0x24, 0x08, 0x04, 0x4c, 0xfb, 0x7d, 0x2f, 0x61, 0xae, 0x83, 0x0b,
            0xc6, 0xac, 0x97, 0x38, 0x60, 0xa2, 0x87, 0xcc, 0x48, 0x92, 0x8c, 0xdf, 0x8e, 0x35,
            0x81, 0xe9, 0x48, 0xd1, 0x2b, 0x48, 0xf6, 0xfc, 0xf2, 0xb1, 0x2b, 0x18, 0xda, 0xa4,
            0x5a, 0x67, 0x95, 0xcc, 0x48, 0x62, 0xab, 0xee
        ]
    );
}

#[test]
fn dump_first_privkey() {
    // Helper to print every scalar so we can paste into the regression test.
    // Run: cargo test --test mnemonic dump_first_privkey -- --nocapture
    let kp = WinternitzKeypair::from_mnemonic(MNEMONIC, 0).expect("valid");
    let sk: WinternitzPrivkey<16> = kp.derive();
    println!("{}", sk);
}

// Canonical N=16 privkey for MNEMONIC at wallet=0, parent=0, child=0.
// Order: 16 message scalars then 2 checksum scalars, each 32 bytes (576 bytes total).
const EXPECTED_PRIVKEY_W0_P0_C0: [u8; 576] = [
    // scalar 0
    164, 137, 216, 51, 111, 169, 142, 239, 113, 82, 90, 41, 78, 207, 49, 227, 244, 190, 165, 199,
    162, 239, 152, 191, 15, 151, 156, 180, 239, 59, 94, 121, // scalar 1
    207, 124, 166, 121, 16, 71, 244, 185, 242, 104, 96, 172, 92, 55, 125, 203, 63, 116, 211, 100,
    218, 136, 232, 13, 187, 32, 204, 70, 54, 50, 220, 120, // scalar 2
    223, 122, 18, 168, 19, 165, 94, 179, 124, 143, 105, 40, 158, 170, 204, 70, 77, 200, 166, 209,
    236, 204, 144, 7, 49, 3, 123, 94, 254, 226, 102, 181, // scalar 3
    215, 199, 114, 43, 100, 214, 123, 187, 78, 2, 173, 37, 248, 167, 168, 2, 136, 2, 237, 158, 77,
    255, 163, 104, 12, 179, 167, 119, 192, 57, 153, 31, // scalar 4
    156, 76, 29, 198, 170, 192, 55, 135, 99, 152, 0, 212, 156, 157, 130, 98, 105, 100, 11, 93, 81,
    59, 109, 94, 99, 13, 138, 205, 184, 51, 106, 179, // scalar 5
    180, 131, 81, 17, 181, 199, 222, 179, 52, 251, 233, 24, 175, 82, 89, 13, 193, 134, 37, 79, 150,
    8, 214, 14, 97, 44, 110, 120, 211, 81, 200, 147, // scalar 6
    56, 93, 240, 124, 211, 76, 83, 222, 69, 197, 74, 132, 52, 24, 63, 132, 186, 192, 146, 92, 29,
    100, 4, 185, 30, 132, 140, 128, 208, 254, 58, 181, // scalar 7
    12, 234, 70, 252, 159, 157, 73, 54, 217, 213, 218, 131, 236, 31, 232, 21, 162, 19, 201, 221,
    150, 237, 44, 227, 157, 68, 237, 224, 9, 81, 110, 240, // scalar 8
    192, 73, 179, 130, 216, 109, 35, 127, 63, 67, 174, 85, 35, 15, 146, 171, 20, 79, 116, 128, 91,
    113, 137, 118, 103, 237, 34, 36, 200, 169, 126, 165, // scalar 9
    1, 17, 193, 84, 247, 105, 220, 17, 94, 113, 170, 87, 199, 8, 41, 129, 73, 203, 222, 213, 105,
    9, 166, 115, 153, 192, 26, 210, 8, 45, 239, 232, // scalar 10
    29, 171, 31, 245, 66, 80, 126, 65, 28, 22, 123, 22, 174, 241, 212, 74, 19, 114, 62, 139, 63,
    166, 165, 60, 197, 35, 51, 7, 250, 251, 240, 76, // scalar 11
    223, 39, 103, 20, 48, 175, 130, 56, 207, 16, 20, 192, 193, 100, 139, 26, 243, 129, 241, 144,
    216, 151, 91, 33, 36, 20, 127, 81, 207, 157, 228, 109, // scalar 12
    149, 11, 236, 180, 208, 73, 202, 141, 4, 8, 181, 3, 198, 219, 5, 52, 186, 226, 70, 196, 74, 61,
    225, 40, 18, 47, 193, 202, 147, 228, 36, 202, // scalar 13
    2, 255, 74, 1, 125, 19, 36, 193, 172, 123, 97, 71, 58, 67, 151, 4, 25, 215, 29, 48, 72, 21,
    143, 241, 71, 238, 121, 21, 69, 228, 118, 215, // scalar 14
    108, 32, 184, 128, 177, 243, 78, 38, 239, 253, 2, 35, 191, 138, 152, 44, 97, 185, 173, 244,
    181, 81, 184, 251, 143, 43, 16, 2, 8, 180, 168, 100, // scalar 15
    115, 153, 207, 40, 28, 81, 101, 23, 113, 240, 6, 131, 170, 255, 132, 40, 192, 161, 213, 79, 27,
    32, 232, 251, 144, 214, 223, 59, 89, 124, 130, 158, // checksum 0
    93, 46, 165, 71, 114, 41, 246, 137, 12, 141, 95, 7, 180, 18, 213, 197, 31, 58, 188, 91, 135,
    251, 33, 230, 110, 47, 76, 90, 124, 239, 128, 125, // checksum 1
    66, 231, 115, 99, 93, 127, 202, 150, 2, 62, 233, 121, 122, 138, 241, 229, 8, 138, 133, 41, 125,
    139, 108, 9, 227, 127, 80, 87, 189, 114, 145, 60,
];

#[test]
fn derive_matches_canonical_scalars() {
    let kp = WinternitzKeypair::from_mnemonic(MNEMONIC, 0).expect("valid");
    let sk: WinternitzPrivkey<16> = kp.derive();
    assert_eq!(sk.as_bytes(), &EXPECTED_PRIVKEY_W0_P0_C0[..]);
}

#[test]
fn rejects_garbage_mnemonic() {
    assert!(WinternitzKeypair::seed("not real bip39 words at all").is_err());
}