@pezkuwi/wasm-crypto 7.5.12 → 7.5.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +50 -0
- package/Xargo.toml +2 -0
- package/build/README.md +29 -0
- package/build/bundle-pezkuwi-wasm-crypto.js +2092 -0
- package/build/bundle.d.ts +40 -0
- package/build/bundle.js +505 -0
- package/build/cjs/bundle.d.ts +40 -0
- package/build/cjs/bundle.js +544 -0
- package/{index.d.ts → build/cjs/index.d.ts} +0 -1
- package/build/cjs/index.js +4 -0
- package/build/cjs/init.js +21 -0
- package/build/cjs/initNone.js +20 -0
- package/build/cjs/initOnlyAsm.js +20 -0
- package/build/cjs/initOnlyWasm.js +20 -0
- package/build/cjs/initWasmAsm.js +20 -0
- package/build/cjs/packageInfo.js +4 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/init.d.ts +16 -0
- package/build/init.js +17 -0
- package/build/initNone.d.ts +10 -0
- package/build/initNone.js +17 -0
- package/build/initOnlyAsm.d.ts +10 -0
- package/build/initOnlyAsm.js +17 -0
- package/build/initOnlyWasm.d.ts +10 -0
- package/build/initOnlyWasm.js +17 -0
- package/build/initWasmAsm.d.ts +10 -0
- package/build/initWasmAsm.js +17 -0
- package/build/package.json +208 -0
- package/build/packageDetect.d.ts +1 -0
- package/build/packageDetect.js +8 -0
- package/build/packageInfo.d.ts +6 -0
- package/build/packageInfo.js +1 -0
- package/build-deno/README.md +25 -0
- package/build-deno/bundle.ts +597 -0
- package/build-deno/index.ts +2 -0
- package/build-deno/init.ts +23 -0
- package/build-deno/initNone.ts +21 -0
- package/build-deno/initOnlyAsm.ts +21 -0
- package/build-deno/initOnlyWasm.ts +21 -0
- package/build-deno/initWasmAsm.ts +21 -0
- package/build-deno/mod.ts +2 -0
- package/build-deno/packageDetect.ts +12 -0
- package/build-deno/packageInfo.ts +3 -0
- package/build-deno/rs/.editorconfig +10 -0
- package/build-tsc/bundle.d.ts +40 -0
- package/{cjs → build-tsc}/index.d.ts +0 -1
- package/build-tsc-cjs/bundle.js +544 -0
- package/{cjs → build-tsc-cjs}/index.js +0 -1
- package/build-tsc-cjs/packageDetect.js +10 -0
- package/{cjs → build-tsc-cjs}/packageInfo.js +1 -1
- package/build-tsc-esm/bundle.js +505 -0
- package/{index.js → build-tsc-esm/index.js} +0 -1
- package/{packageInfo.js → build-tsc-esm/packageInfo.js} +1 -1
- package/package.json +89 -87
- package/src/bundle.ts +613 -0
- package/src/index.ts +5 -0
- package/src/init.ts +25 -0
- package/src/initNone.ts +23 -0
- package/src/initOnlyAsm.ts +23 -0
- package/src/initOnlyWasm.ts +23 -0
- package/src/initWasmAsm.ts +23 -0
- package/src/lib.rs +24 -0
- package/src/mod.ts +4 -0
- package/src/packageDetect.ts +16 -0
- package/src/packageInfo.ts +6 -0
- package/src/rs/.editorconfig +10 -0
- package/src/rs/bip39.rs +139 -0
- package/src/rs/ed25519.rs +142 -0
- package/src/rs/hashing.rs +322 -0
- package/src/rs/secp256k1.rs +150 -0
- package/src/rs/sr25519.rs +331 -0
- package/src/rs/vrf.rs +144 -0
- package/test/all/bip39.js +86 -0
- package/test/all/ed25519.js +84 -0
- package/test/all/hashing.js +138 -0
- package/test/all/index.js +126 -0
- package/test/all/secp256k1.js +105 -0
- package/test/all/sr25519.js +211 -0
- package/test/all/vrf.js +74 -0
- package/test/asm.js +10 -0
- package/test/deno.ts +38 -0
- package/test/jest.spec.ts +25 -0
- package/test/loader-build.js +39 -0
- package/test/wasm.js +8 -0
- package/tsconfig.build.json +19 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.spec.json +16 -0
- package/tsconfig.spec.tsbuildinfo +1 -0
- package/bundle-pezkuwi-wasm-crypto.js +0 -777
- package/bundle.d.ts +0 -37
- package/bundle.js +0 -165
- package/cjs/bundle.d.ts +0 -37
- package/cjs/bundle.js +0 -171
- /package/{LICENSE → build/LICENSE} +0 -0
- /package/{init.d.ts → build/cjs/init.d.ts} +0 -0
- /package/{initNone.d.ts → build/cjs/initNone.d.ts} +0 -0
- /package/{initOnlyAsm.d.ts → build/cjs/initOnlyAsm.d.ts} +0 -0
- /package/{initOnlyWasm.d.ts → build/cjs/initOnlyWasm.d.ts} +0 -0
- /package/{initWasmAsm.d.ts → build/cjs/initWasmAsm.d.ts} +0 -0
- /package/{cjs → build/cjs}/package.json +0 -0
- /package/{packageDetect.d.ts → build/cjs/packageDetect.d.ts} +0 -0
- /package/{cjs → build/cjs}/packageDetect.js +0 -0
- /package/{packageInfo.d.ts → build/cjs/packageInfo.d.ts} +0 -0
- /package/{cjs → build-tsc}/init.d.ts +0 -0
- /package/{cjs → build-tsc}/initNone.d.ts +0 -0
- /package/{cjs → build-tsc}/initOnlyAsm.d.ts +0 -0
- /package/{cjs → build-tsc}/initOnlyWasm.d.ts +0 -0
- /package/{cjs → build-tsc}/initWasmAsm.d.ts +0 -0
- /package/{cjs → build-tsc}/packageDetect.d.ts +0 -0
- /package/{cjs → build-tsc}/packageInfo.d.ts +0 -0
- /package/{cjs → build-tsc-cjs}/init.js +0 -0
- /package/{cjs → build-tsc-cjs}/initNone.js +0 -0
- /package/{cjs → build-tsc-cjs}/initOnlyAsm.js +0 -0
- /package/{cjs → build-tsc-cjs}/initOnlyWasm.js +0 -0
- /package/{cjs → build-tsc-cjs}/initWasmAsm.js +0 -0
- /package/{init.js → build-tsc-esm/init.js} +0 -0
- /package/{initNone.js → build-tsc-esm/initNone.js} +0 -0
- /package/{initOnlyAsm.js → build-tsc-esm/initOnlyAsm.js} +0 -0
- /package/{initOnlyWasm.js → build-tsc-esm/initOnlyWasm.js} +0 -0
- /package/{initWasmAsm.js → build-tsc-esm/initWasmAsm.js} +0 -0
- /package/{packageDetect.js → build-tsc-esm/packageDetect.js} +0 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
// Copyright 2019-2025 @pezkuwi/wasm-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Based on original work by Paritytech (https://github.com/pezkuwichain/schnorrkel-js/)
|
|
5
|
+
// Adapted for PezkuwiChain by Kurdistan Tech Institute
|
|
6
|
+
// Original fork history: pezkuwichain/schnorrkel-js → pezkuwi-js/schnorrkel-js → pezkuwi/wasm
|
|
7
|
+
|
|
8
|
+
use curve25519_dalek::scalar::Scalar;
|
|
9
|
+
use schnorrkel::{
|
|
10
|
+
ExpansionMode, Keypair, MiniSecretKey, PublicKey, SecretKey, Signature,
|
|
11
|
+
derive::{Derivation, ChainCode, CHAIN_CODE_LENGTH},
|
|
12
|
+
};
|
|
13
|
+
use wasm_bindgen::prelude::*;
|
|
14
|
+
|
|
15
|
+
// We must make sure that this is the same as declared in the bizinikiwi source code.
|
|
16
|
+
// PezkuwiChain uses "bizinikiwi" as the signing context instead of "bizinikiwi".
|
|
17
|
+
const CTX: &'static [u8] = b"bizinikiwi";
|
|
18
|
+
|
|
19
|
+
/// ChainCode construction helper
|
|
20
|
+
fn new_cc(data: &[u8]) -> ChainCode {
|
|
21
|
+
let mut cc = [0u8; CHAIN_CODE_LENGTH];
|
|
22
|
+
|
|
23
|
+
cc.copy_from_slice(&data);
|
|
24
|
+
|
|
25
|
+
ChainCode(cc)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Perform a derivation on a secret
|
|
29
|
+
///
|
|
30
|
+
/// * secret: UIntArray with 64 bytes
|
|
31
|
+
/// * cc: UIntArray with 32 bytes
|
|
32
|
+
///
|
|
33
|
+
/// returned vector the derived keypair as a array of 96 bytes
|
|
34
|
+
#[wasm_bindgen]
|
|
35
|
+
pub fn ext_sr_derive_keypair_hard(pair: &[u8], cc: &[u8]) -> Vec<u8> {
|
|
36
|
+
match Keypair::from_half_ed25519_bytes(pair) {
|
|
37
|
+
Ok(p) => p.secret
|
|
38
|
+
.hard_derive_mini_secret_key(Some(new_cc(cc)), &[]).0
|
|
39
|
+
.expand_to_keypair(ExpansionMode::Ed25519)
|
|
40
|
+
.to_half_ed25519_bytes()
|
|
41
|
+
.to_vec(),
|
|
42
|
+
_ => panic!("Invalid pair provided.")
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Perform a derivation on a secret
|
|
47
|
+
///
|
|
48
|
+
/// * secret: UIntArray with 64 bytes
|
|
49
|
+
/// * cc: UIntArray with 32 bytes
|
|
50
|
+
///
|
|
51
|
+
/// returned vector the derived keypair as a array of 96 bytes
|
|
52
|
+
#[wasm_bindgen]
|
|
53
|
+
pub fn ext_sr_derive_keypair_soft(pair: &[u8], cc: &[u8]) -> Vec<u8> {
|
|
54
|
+
match Keypair::from_half_ed25519_bytes(pair) {
|
|
55
|
+
Ok(p) => p
|
|
56
|
+
.derived_key_simple(new_cc(cc), &[]).0
|
|
57
|
+
.to_half_ed25519_bytes()
|
|
58
|
+
.to_vec(),
|
|
59
|
+
_ => panic!("Invalid pair provided.")
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Perform a derivation on a publicKey
|
|
64
|
+
///
|
|
65
|
+
/// * pubkey: UIntArray with 32 bytes
|
|
66
|
+
/// * cc: UIntArray with 32 bytes
|
|
67
|
+
///
|
|
68
|
+
/// returned vector is the derived publicKey as a array of 32 bytes
|
|
69
|
+
#[wasm_bindgen]
|
|
70
|
+
pub fn ext_sr_derive_public_soft(pubkey: &[u8], cc: &[u8]) -> Vec<u8> {
|
|
71
|
+
match PublicKey::from_bytes(pubkey) {
|
|
72
|
+
Ok(k) => k
|
|
73
|
+
.derived_key_simple(new_cc(cc), &[]).0
|
|
74
|
+
.to_bytes()
|
|
75
|
+
.to_vec(),
|
|
76
|
+
_ => panic!("Invalid pubkey provided.")
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Generate a key pair.
|
|
81
|
+
///
|
|
82
|
+
/// * seed: UIntArray with 32 element
|
|
83
|
+
///
|
|
84
|
+
/// returned vector is the concatenation of first the private key (64 bytes)
|
|
85
|
+
/// followed by the public key (32) bytes.
|
|
86
|
+
#[wasm_bindgen]
|
|
87
|
+
pub fn ext_sr_from_seed(seed: &[u8]) -> Vec<u8> {
|
|
88
|
+
match MiniSecretKey::from_bytes(seed) {
|
|
89
|
+
Ok(s) => s
|
|
90
|
+
.expand_to_keypair(ExpansionMode::Ed25519)
|
|
91
|
+
.to_half_ed25519_bytes()
|
|
92
|
+
.to_vec(),
|
|
93
|
+
_ => panic!("Invalid seed provided.")
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Generate a key pair from a known pair. (This is not exposed via WASM)
|
|
98
|
+
///
|
|
99
|
+
/// * seed: UIntArray with 96 element
|
|
100
|
+
///
|
|
101
|
+
/// returned vector is the concatenation of first the private key (64 bytes)
|
|
102
|
+
/// followed by the public key (32) bytes.
|
|
103
|
+
pub fn ext_sr_from_pair(pair: &[u8]) -> Vec<u8> {
|
|
104
|
+
match Keypair::from_half_ed25519_bytes(pair) {
|
|
105
|
+
Ok(p) => p
|
|
106
|
+
.to_half_ed25519_bytes()
|
|
107
|
+
.to_vec(),
|
|
108
|
+
_ => panic!("Invalid pair provided.")
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Sign a message
|
|
113
|
+
///
|
|
114
|
+
/// The combination of both public and private key must be provided.
|
|
115
|
+
/// This is effectively equivalent to a keypair.
|
|
116
|
+
///
|
|
117
|
+
/// * pubkey: UIntArray with 32 element
|
|
118
|
+
/// * private: UIntArray with 64 element
|
|
119
|
+
/// * message: Arbitrary length UIntArray
|
|
120
|
+
///
|
|
121
|
+
/// * returned vector is the signature consisting of 64 bytes.
|
|
122
|
+
#[wasm_bindgen]
|
|
123
|
+
pub fn ext_sr_sign(pubkey: &[u8], secret: &[u8], message: &[u8]) -> Vec<u8> {
|
|
124
|
+
match (SecretKey::from_ed25519_bytes(secret), PublicKey::from_bytes(pubkey)) {
|
|
125
|
+
(Ok(s), Ok(k)) => s
|
|
126
|
+
.sign_simple(CTX, message, &k)
|
|
127
|
+
.to_bytes()
|
|
128
|
+
.to_vec(),
|
|
129
|
+
_ => panic!("Invalid secret or pubkey provided.")
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// Verify a message and its corresponding against a public key;
|
|
134
|
+
///
|
|
135
|
+
/// * signature: UIntArray with 64 element
|
|
136
|
+
/// * message: Arbitrary length UIntArray
|
|
137
|
+
/// * pubkey: UIntArray with 32 element
|
|
138
|
+
#[wasm_bindgen]
|
|
139
|
+
pub fn ext_sr_verify(signature: &[u8], message: &[u8], pubkey: &[u8]) -> bool {
|
|
140
|
+
match (Signature::from_bytes(signature), PublicKey::from_bytes(pubkey)) {
|
|
141
|
+
(Ok(s), Ok(k)) => k
|
|
142
|
+
.verify_simple(CTX, message, &s)
|
|
143
|
+
.is_ok(),
|
|
144
|
+
_ => false
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Key agreement between other's public key and self secret key.
|
|
149
|
+
///
|
|
150
|
+
/// * pubkey: UIntArray with 32 element
|
|
151
|
+
/// * secret: UIntArray with 64 element
|
|
152
|
+
///
|
|
153
|
+
/// * returned vector is the generated secret of 32 bytes.
|
|
154
|
+
#[wasm_bindgen]
|
|
155
|
+
pub fn ext_sr_agree(pubkey: &[u8], secret: &[u8]) -> Vec<u8> {
|
|
156
|
+
match SecretKey::from_ed25519_bytes(secret) {
|
|
157
|
+
Ok(s) => {
|
|
158
|
+
// The first 32 bytes holds the canonical private key
|
|
159
|
+
let mut key = [0u8; 32];
|
|
160
|
+
|
|
161
|
+
key.copy_from_slice(&s.to_bytes()[0..32]);
|
|
162
|
+
|
|
163
|
+
match (Scalar::from_canonical_bytes(key), PublicKey::from_bytes(pubkey)) {
|
|
164
|
+
(Some(n), Ok(k)) => (&n * k.as_point())
|
|
165
|
+
.compress().0
|
|
166
|
+
.to_vec(),
|
|
167
|
+
_ => panic!("Invalid scalar or pubkey provided.")
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
_ => panic!("Invalid secret provided.")
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#[cfg(test)]
|
|
175
|
+
pub mod tests {
|
|
176
|
+
extern crate rand;
|
|
177
|
+
extern crate schnorrkel;
|
|
178
|
+
|
|
179
|
+
use super::*;
|
|
180
|
+
use hex_literal::hex;
|
|
181
|
+
use schnorrkel::{SIGNATURE_LENGTH, KEYPAIR_LENGTH, SECRET_KEY_LENGTH};
|
|
182
|
+
|
|
183
|
+
fn generate_random_seed() -> Vec<u8> {
|
|
184
|
+
(0..32).map(|_| rand::random::<u8>()).collect()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#[test]
|
|
188
|
+
fn can_new_keypair() {
|
|
189
|
+
let seed = generate_random_seed();
|
|
190
|
+
let keypair = ext_sr_from_seed(seed.as_slice());
|
|
191
|
+
|
|
192
|
+
assert!(keypair.len() == KEYPAIR_LENGTH);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#[test]
|
|
196
|
+
fn creates_pair_from_known_seed() {
|
|
197
|
+
let seed = hex!("fac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e");
|
|
198
|
+
let expected = hex!("46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a");
|
|
199
|
+
let keypair = ext_sr_from_seed(&seed);
|
|
200
|
+
let public = &keypair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH];
|
|
201
|
+
|
|
202
|
+
assert_eq!(public, expected);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
#[test]
|
|
206
|
+
fn new_pair_from_known_pair() {
|
|
207
|
+
let input = hex!("28b0ae221c6bb06856b287f60d7ea0d98552ea5a16db16956849aa371db3eb51fd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca3446ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a");
|
|
208
|
+
let keypair = ext_sr_from_pair(&input);
|
|
209
|
+
let expected = hex!("46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a");
|
|
210
|
+
let public = &keypair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH];
|
|
211
|
+
|
|
212
|
+
assert_eq!(public, expected);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#[test]
|
|
216
|
+
fn can_sign_message() {
|
|
217
|
+
let seed = generate_random_seed();
|
|
218
|
+
let keypair = ext_sr_from_seed(seed.as_slice());
|
|
219
|
+
let private = &keypair[0..SECRET_KEY_LENGTH];
|
|
220
|
+
let public = &keypair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH];
|
|
221
|
+
let message = b"this is a message";
|
|
222
|
+
let signature = ext_sr_sign(public, private, message);
|
|
223
|
+
|
|
224
|
+
assert!(signature.len() == SIGNATURE_LENGTH);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#[test]
|
|
228
|
+
fn can_verify_message() {
|
|
229
|
+
let seed = generate_random_seed();
|
|
230
|
+
let keypair = ext_sr_from_seed(seed.as_slice());
|
|
231
|
+
let private = &keypair[0..SECRET_KEY_LENGTH];
|
|
232
|
+
let public = &keypair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH];
|
|
233
|
+
let message = b"this is a message";
|
|
234
|
+
let signature = ext_sr_sign(public, private, message);
|
|
235
|
+
let is_valid = ext_sr_verify(&signature[..], message, public);
|
|
236
|
+
|
|
237
|
+
assert!(is_valid);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
#[test]
|
|
241
|
+
fn can_verify_known_message() {
|
|
242
|
+
let message = b"I hereby verify that I control 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
|
|
243
|
+
let public = hex!("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d");
|
|
244
|
+
let signature = hex!("1037eb7e51613d0dcf5930ae518819c87d655056605764840d9280984e1b7063c4566b55bf292fcab07b369d01095879b50517beca4d26e6a65866e25fec0d83");
|
|
245
|
+
let is_valid = ext_sr_verify(&signature, message, &public);
|
|
246
|
+
|
|
247
|
+
assert!(is_valid);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#[test]
|
|
251
|
+
fn can_verify_known_wrapped_message() {
|
|
252
|
+
let message = b"<Bytes>message to sign</Bytes>";
|
|
253
|
+
let public = hex!("f84d048da2ddae2d9d8fd6763f469566e8817a26114f39408de15547f6d47805");
|
|
254
|
+
let signature = hex!("48ce2c90e08651adfc8ecef84e916f6d1bb51ebebd16150ee12df247841a5437951ea0f9d632ca165e6ab391532e75e701be6a1caa88c8a6bcca3511f55b4183");
|
|
255
|
+
let is_valid = ext_sr_verify(&signature, message, &public);
|
|
256
|
+
|
|
257
|
+
assert!(is_valid);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
#[test]
|
|
261
|
+
fn can_verify_known_wrapped_message_fail() {
|
|
262
|
+
let message = b"message to sign";
|
|
263
|
+
let public = hex!("f84d048da2ddae2d9d8fd6763f469566e8817a26114f39408de15547f6d47805");
|
|
264
|
+
let signature = hex!("48ce2c90e08651adfc8ecef84e916f6d1bb51ebebd16150ee12df247841a5437951ea0f9d632ca165e6ab391532e75e701be6a1caa88c8a6bcca3511f55b4183");
|
|
265
|
+
let is_valid = ext_sr_verify(&signature, message, &public);
|
|
266
|
+
|
|
267
|
+
assert!(!is_valid);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
#[test]
|
|
271
|
+
fn soft_derives_pair() {
|
|
272
|
+
let cc = hex!("0c666f6f00000000000000000000000000000000000000000000000000000000"); // foo
|
|
273
|
+
let seed = hex!("fac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e");
|
|
274
|
+
let expected = hex!("40b9675df90efa6069ff623b0fdfcf706cd47ca7452a5056c7ad58194d23440a");
|
|
275
|
+
let keypair = ext_sr_from_seed(&seed);
|
|
276
|
+
let derived = ext_sr_derive_keypair_soft(&keypair, &cc);
|
|
277
|
+
let public = &derived[SECRET_KEY_LENGTH..KEYPAIR_LENGTH];
|
|
278
|
+
|
|
279
|
+
assert_eq!(public, expected);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
#[test]
|
|
283
|
+
fn soft_derives_public() {
|
|
284
|
+
let cc = hex!("0c666f6f00000000000000000000000000000000000000000000000000000000"); // foo
|
|
285
|
+
let public = hex!("46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a");
|
|
286
|
+
let expected = hex!("40b9675df90efa6069ff623b0fdfcf706cd47ca7452a5056c7ad58194d23440a");
|
|
287
|
+
let derived = ext_sr_derive_public_soft(&public, &cc);
|
|
288
|
+
|
|
289
|
+
assert_eq!(derived, expected);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
#[test]
|
|
293
|
+
fn hard_derives_pair() {
|
|
294
|
+
let cc = hex!("14416c6963650000000000000000000000000000000000000000000000000000"); // Alice
|
|
295
|
+
let seed = hex!("fac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e");
|
|
296
|
+
let expected = hex!("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d");
|
|
297
|
+
let keypair = ext_sr_from_seed(&seed);
|
|
298
|
+
let derived = ext_sr_derive_keypair_hard(&keypair, &cc);
|
|
299
|
+
let public = &derived[SECRET_KEY_LENGTH..KEYPAIR_LENGTH];
|
|
300
|
+
|
|
301
|
+
assert_eq!(public, expected);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
#[test]
|
|
305
|
+
fn key_agreement() {
|
|
306
|
+
let self_seed = hex!("98b3d305d5a5eace562387e47e59badd4d77e3f72cabfb10a60f8a197059f0a8");
|
|
307
|
+
let other_seed = hex!("9732eea001851ff862d949a1699c9971f3a26edbede2ad7922cbbe9a0701f366");
|
|
308
|
+
let expected = hex!("b03a0b198c34c16f35cae933d88b16341b4cef3e84e851f20e664c6a30527f4e");
|
|
309
|
+
let self_pair = ext_sr_from_seed(&self_seed);
|
|
310
|
+
let self_sk = &self_pair[0..SECRET_KEY_LENGTH];
|
|
311
|
+
let self_pk = &self_pair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH];
|
|
312
|
+
let other_pair = ext_sr_from_seed(&other_seed);
|
|
313
|
+
let other_sk = &other_pair[0..SECRET_KEY_LENGTH];
|
|
314
|
+
let other_pk = &other_pair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH];
|
|
315
|
+
|
|
316
|
+
assert_eq!(ext_sr_agree(self_pk, other_sk), expected);
|
|
317
|
+
assert_eq!(ext_sr_agree(other_pk, self_sk), expected);
|
|
318
|
+
|
|
319
|
+
let seed = generate_random_seed();
|
|
320
|
+
let self_pair = ext_sr_from_seed(seed.as_slice());
|
|
321
|
+
let self_sk = &self_pair[0..SECRET_KEY_LENGTH];
|
|
322
|
+
let self_pk = &self_pair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH];
|
|
323
|
+
|
|
324
|
+
let seed = generate_random_seed();
|
|
325
|
+
let other_pair = ext_sr_from_seed(seed.as_slice());
|
|
326
|
+
let other_sk = &other_pair[0..SECRET_KEY_LENGTH];
|
|
327
|
+
let other_pk = &other_pair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH];
|
|
328
|
+
|
|
329
|
+
assert_eq!(ext_sr_agree(self_pk, other_sk), ext_sr_agree(other_pk, self_sk));
|
|
330
|
+
}
|
|
331
|
+
}
|
package/src/rs/vrf.rs
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Copyright 2019-2025 @pezkuwi/wasm-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Copyright 2019 Paritytech via https://github.com/pezkuwichain/schnorrkel-js/
|
|
5
|
+
//
|
|
6
|
+
// Originally developed (as a fork) in https://github.com/pezkuwi-js/schnorrkel-js/
|
|
7
|
+
// which was adpated from the initial https://github.com/pezkuwichain/schnorrkel-js/
|
|
8
|
+
// forked at commit eff430ddc3090f56317c80654208b8298ef7ab3f
|
|
9
|
+
|
|
10
|
+
use merlin::Transcript;
|
|
11
|
+
use schnorrkel::{signing_context, vrf::{VRFOutput, VRFProof}, PublicKey, SecretKey};
|
|
12
|
+
use wasm_bindgen::prelude::*;
|
|
13
|
+
|
|
14
|
+
/// Size of VRF output, bytes
|
|
15
|
+
pub const OUTPUT_SIZE: usize = 32;
|
|
16
|
+
|
|
17
|
+
/// Size of VRF proof, bytes
|
|
18
|
+
pub const PROOF_SIZE: usize = 64;
|
|
19
|
+
|
|
20
|
+
/// Size of VRF raw output, bytes
|
|
21
|
+
pub const RAW_OUTPUT_SIZE: usize = 16;
|
|
22
|
+
|
|
23
|
+
/// Size of VRF limit, bytes
|
|
24
|
+
pub const THRESHOLD_SIZE: usize = 16;
|
|
25
|
+
|
|
26
|
+
/// Size of the final full output
|
|
27
|
+
pub const RESULT_SIZE: usize = OUTPUT_SIZE + PROOF_SIZE;
|
|
28
|
+
|
|
29
|
+
pub fn new_transcript(extra: &[u8]) -> Transcript {
|
|
30
|
+
let mut transcript = Transcript::new(b"VRF");
|
|
31
|
+
|
|
32
|
+
// if is important that we don't append when empty, since the
|
|
33
|
+
// append in merging encodes length information
|
|
34
|
+
if extra.len() > 0 {
|
|
35
|
+
transcript.append_message(b"", extra);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
transcript
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// We don't use the non-extra sign and verify internally since in sr25519 they only wrap -
|
|
42
|
+
//
|
|
43
|
+
// https://github.com/w3f/schnorrkel/blob/8fa2ad3e9fbf0b652c724df6a87a4b3c5500f759/src/vrf.rs#L660
|
|
44
|
+
|
|
45
|
+
/// Run a Random Verifiable Function (VRF) on one single input
|
|
46
|
+
/// (message) transcript, and an extra message transcript,
|
|
47
|
+
/// producing the output signature and corresponding short proof.
|
|
48
|
+
///
|
|
49
|
+
/// * secret: UIntArray with 64 element
|
|
50
|
+
/// * context: Arbitrary length UIntArray
|
|
51
|
+
/// * message: Arbitrary length UIntArray
|
|
52
|
+
/// * extra: Arbitrary length UIntArray
|
|
53
|
+
///
|
|
54
|
+
/// * returned vector is the 32-byte output (signature) and 64-byte proof.
|
|
55
|
+
#[wasm_bindgen]
|
|
56
|
+
pub fn ext_vrf_sign(secret: &[u8], ctx: &[u8], msg: &[u8], extra: &[u8]) -> Vec<u8> {
|
|
57
|
+
match SecretKey::from_ed25519_bytes(secret) {
|
|
58
|
+
Ok(s) => {
|
|
59
|
+
let mut res: [u8; RESULT_SIZE] = [0u8; RESULT_SIZE];
|
|
60
|
+
let (io, proof, _) = s
|
|
61
|
+
.to_keypair()
|
|
62
|
+
.vrf_sign_extra(signing_context(ctx).bytes(msg), new_transcript(extra));
|
|
63
|
+
|
|
64
|
+
res[..OUTPUT_SIZE].copy_from_slice(io.as_output_bytes());
|
|
65
|
+
res[OUTPUT_SIZE..].copy_from_slice(&proof.to_bytes());
|
|
66
|
+
|
|
67
|
+
res.to_vec()
|
|
68
|
+
},
|
|
69
|
+
_ => panic!("Invalid secret provided.")
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Verify VRF proof for one single input transcript, and an extra message transcript,
|
|
74
|
+
/// and corresponding output.
|
|
75
|
+
///
|
|
76
|
+
/// * pubkey: UIntArray with 32 element
|
|
77
|
+
/// * context: Arbitrary length UIntArray
|
|
78
|
+
/// * message: Arbitrary length UIntArray
|
|
79
|
+
/// * extra: Arbitrary length UIntArray
|
|
80
|
+
/// * out_and_proof: 96-byte output & proof array from the ext_sign function.
|
|
81
|
+
#[wasm_bindgen]
|
|
82
|
+
pub fn ext_vrf_verify(pubkey: &[u8], ctx: &[u8], msg: &[u8], extra: &[u8], out: &[u8]) -> bool {
|
|
83
|
+
match (PublicKey::from_bytes(pubkey), VRFOutput::from_bytes(&out[..OUTPUT_SIZE]), VRFProof::from_bytes(&out[OUTPUT_SIZE..RESULT_SIZE])) {
|
|
84
|
+
(Ok(k), Ok(o), Ok(p)) => k
|
|
85
|
+
.vrf_verify_extra(signing_context(ctx).bytes(msg), &o, &p, new_transcript(extra))
|
|
86
|
+
.is_ok(),
|
|
87
|
+
_ => false,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#[cfg(test)]
|
|
92
|
+
pub mod tests {
|
|
93
|
+
extern crate rand;
|
|
94
|
+
extern crate schnorrkel;
|
|
95
|
+
|
|
96
|
+
use super::*;
|
|
97
|
+
use crate::sr25519::ext_sr_from_seed;
|
|
98
|
+
use schnorrkel::{KEYPAIR_LENGTH, SECRET_KEY_LENGTH};
|
|
99
|
+
|
|
100
|
+
fn generate_random_seed() -> Vec<u8> {
|
|
101
|
+
(0..32).map(|_| rand::random::<u8>()).collect()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#[test]
|
|
105
|
+
fn sign_extra_and_verify() {
|
|
106
|
+
let seed = generate_random_seed();
|
|
107
|
+
let keypair = ext_sr_from_seed(seed.as_slice());
|
|
108
|
+
let private = &keypair[0..SECRET_KEY_LENGTH];
|
|
109
|
+
let public = &keypair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH];
|
|
110
|
+
let context = b"my VRF context";
|
|
111
|
+
let message = b"this is a message";
|
|
112
|
+
let extra = b"this is an extra";
|
|
113
|
+
|
|
114
|
+
// Perform multiple sign_extra calls w/ same context, message, extra args
|
|
115
|
+
let out1 = ext_vrf_sign(private, context, message, extra);
|
|
116
|
+
let out2 = ext_vrf_sign(private, context, message, extra);
|
|
117
|
+
|
|
118
|
+
// Basic size checks
|
|
119
|
+
assert!(out1.len() == RESULT_SIZE);
|
|
120
|
+
assert!(out2.len() == RESULT_SIZE);
|
|
121
|
+
|
|
122
|
+
// Given the same context, message & extra, output should be deterministic
|
|
123
|
+
assert_eq!(&out1[..OUTPUT_SIZE], &out2[..OUTPUT_SIZE]);
|
|
124
|
+
|
|
125
|
+
// But proof is non-deterministic
|
|
126
|
+
assert_ne!(&out1[OUTPUT_SIZE..], &out2[OUTPUT_SIZE..]);
|
|
127
|
+
|
|
128
|
+
// VRF outputs can verified w/ original context, message & extra args
|
|
129
|
+
assert!(ext_vrf_verify(
|
|
130
|
+
public,
|
|
131
|
+
context,
|
|
132
|
+
message,
|
|
133
|
+
extra,
|
|
134
|
+
&out1
|
|
135
|
+
));
|
|
136
|
+
assert!(ext_vrf_verify(
|
|
137
|
+
public,
|
|
138
|
+
context,
|
|
139
|
+
message,
|
|
140
|
+
extra,
|
|
141
|
+
&out2
|
|
142
|
+
));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Copyright 2019-2026 @pezkuwi/wasm-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/* global it */
|
|
5
|
+
|
|
6
|
+
import { assert, u8aToHex } from '@pezkuwi/util';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {*} wasm
|
|
10
|
+
*/
|
|
11
|
+
export function bip39Generate (wasm) {
|
|
12
|
+
it('generates a bip39 phrase', () => {
|
|
13
|
+
const res = wasm.bip39Generate(21);
|
|
14
|
+
|
|
15
|
+
// console.log('\tPHR', res);
|
|
16
|
+
|
|
17
|
+
assert(res.split(' ').length === 21, 'ERROR: Invalid bip39 Phase length');
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {*} wasm
|
|
23
|
+
*/
|
|
24
|
+
export function bip39GenerateSubsequent (wasm) {
|
|
25
|
+
it('generates different subsequent mnemonics', () => {
|
|
26
|
+
const val1 = wasm.bip39Generate(24);
|
|
27
|
+
const val2 = wasm.bip39Generate(24);
|
|
28
|
+
|
|
29
|
+
// console.log('\tVL1', val1);
|
|
30
|
+
// console.log('\tVL2', val2);
|
|
31
|
+
|
|
32
|
+
assert(val1 !== val2, 'ERROR: Subsequent mnemonics are the same');
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {*} wasm
|
|
38
|
+
*/
|
|
39
|
+
export function bip39Validate (wasm) {
|
|
40
|
+
it('validates a created mnemonic', () => {
|
|
41
|
+
const res = wasm.bip39Validate(wasm.bip39Generate(12));
|
|
42
|
+
|
|
43
|
+
// console.log('\tVAL', res);
|
|
44
|
+
|
|
45
|
+
assert(res, 'ERROR: Invalid bip39 validation');
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {*} wasm
|
|
51
|
+
*/
|
|
52
|
+
export function bip39ToEntropy (wasm) {
|
|
53
|
+
it('creates correct entropy for a known mnemonic', () => {
|
|
54
|
+
const res = u8aToHex(wasm.bip39ToEntropy('legal winner thank year wave sausage worth useful legal winner thank yellow'));
|
|
55
|
+
|
|
56
|
+
// console.log('\tENT', res);
|
|
57
|
+
|
|
58
|
+
assert(res === '0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f', 'ERROR: Invalid bip39 entropy');
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {*} wasm
|
|
64
|
+
*/
|
|
65
|
+
export function bip39ToMiniSecret (wasm) {
|
|
66
|
+
it('creates the correct minisecret from a mnemonic', () => {
|
|
67
|
+
const res = u8aToHex(wasm.bip39ToMiniSecret('legal winner thank year wave sausage worth useful legal winner thank yellow', 'Bizinikiwi'));
|
|
68
|
+
|
|
69
|
+
// console.log('\tMIN', res);
|
|
70
|
+
|
|
71
|
+
assert(res === '0x4313249608fe8ac10fd5886c92c4579007272cb77c21551ee5b8d60b78041685', 'ERROR: Invalid bip39 mini secret');
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {*} wasm
|
|
77
|
+
*/
|
|
78
|
+
export function bip39ToSeed (wasm) {
|
|
79
|
+
it('creates the correct seed for a mnemonic', () => {
|
|
80
|
+
const res = u8aToHex(wasm.bip39ToSeed('seed sock milk update focus rotate barely fade car face mechanic mercy', ''));
|
|
81
|
+
|
|
82
|
+
// console.log('\tSEE', res);
|
|
83
|
+
|
|
84
|
+
assert(res === '0x3c121e20de068083b49c2315697fb59a2d9e8643c24e5ea7628132c58969a027', 'ERROR: Invalid bip39 mini secret');
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Copyright 2019-2026 @pezkuwi/wasm-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/* global it */
|
|
5
|
+
|
|
6
|
+
import crypto from 'crypto';
|
|
7
|
+
|
|
8
|
+
import { assert, hexToU8a, stringToU8a, u8aToHex } from '@pezkuwi/util';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @internal
|
|
12
|
+
* @param {*} pair
|
|
13
|
+
*/
|
|
14
|
+
function extractKeys (pair) {
|
|
15
|
+
return [pair, pair.slice(32), pair.slice(0, 32)];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @internal
|
|
20
|
+
* @param {*} wasm
|
|
21
|
+
*/
|
|
22
|
+
function randomPair (wasm) {
|
|
23
|
+
return extractKeys(wasm.ed25519KeypairFromSeed(crypto.randomBytes(32)));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {*} wasm
|
|
28
|
+
*/
|
|
29
|
+
export function ed25519PairFromSeed (wasm) {
|
|
30
|
+
it('creates a known pair from a known seed', () => {
|
|
31
|
+
const [pair, pk, sk] = extractKeys(wasm.ed25519KeypairFromSeed(stringToU8a('12345678901234567890123456789012')));
|
|
32
|
+
|
|
33
|
+
// console.log('\tSEC', u8aToHex(sk));
|
|
34
|
+
// console.log('\tPUB', u8aToHex(pk));
|
|
35
|
+
|
|
36
|
+
assert(u8aToHex(sk) === '0x3132333435363738393031323334353637383930313233343536373839303132', 'Secretkey does not match');
|
|
37
|
+
assert(u8aToHex(pk) === '0x2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee', 'Public key does not match');
|
|
38
|
+
assert(u8aToHex(pair) === '0x31323334353637383930313233343536373839303132333435363738393031322f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee', 'ERROR: pairFromSeed() does not match');
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {*} wasm
|
|
44
|
+
*/
|
|
45
|
+
export function ed25519SignAndVerify (wasm) {
|
|
46
|
+
it('signs and verifies', () => {
|
|
47
|
+
const [, pk, sk] = randomPair(wasm);
|
|
48
|
+
const signature = wasm.ed25519Sign(pk, sk, stringToU8a('this is a message'));
|
|
49
|
+
const isValid = wasm.ed25519Verify(signature, stringToU8a('this is a message'), pk);
|
|
50
|
+
|
|
51
|
+
// console.log('\tSIG', u8aToHex(signature));
|
|
52
|
+
// console.log('\tRES', isValid);
|
|
53
|
+
|
|
54
|
+
assert(isValid, 'ERROR: Unable to verify signature');
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {*} wasm
|
|
60
|
+
*/
|
|
61
|
+
export function ed25519VerifyExisting (wasm) {
|
|
62
|
+
it('verifies a known signature', () => {
|
|
63
|
+
const isValid = wasm.ed25519Verify(hexToU8a('0x90588f3f512496f2dd40571d162e8182860081c74e2085316e7c4396918f07da412ee029978e4dd714057fe973bd9e7d645148bf7b66680d67c93227cde95202'), stringToU8a('this is a message'), hexToU8a('0x2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee'));
|
|
64
|
+
|
|
65
|
+
// console.log('\tRES', isValid);
|
|
66
|
+
|
|
67
|
+
assert(isValid, 'ERROR: Unable to verify signature');
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {*} wasm
|
|
73
|
+
*/
|
|
74
|
+
export function ed25519Benchmark (wasm) {
|
|
75
|
+
it('runs a verification benchamark', () => {
|
|
76
|
+
const MESSAGE = stringToU8a('this is a message');
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < 256; i++) {
|
|
79
|
+
const [, pk, sk] = randomPair(wasm);
|
|
80
|
+
|
|
81
|
+
assert(wasm.ed25519Verify(wasm.ed25519Sign(pk, sk, MESSAGE), MESSAGE, pk), 'ERROR: Unable to verify signature');
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|