@pezkuwi/wasm-crypto 7.5.7 → 7.5.8
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/README.md +5 -5
- package/Xargo.toml +2 -0
- package/package.json +19 -165
- package/src/bundle.ts +247 -0
- package/src/index.ts +6 -0
- package/{init.d.ts → src/init.ts} +11 -2
- package/{initNone.js → src/initNone.ts} +10 -4
- package/{initOnlyAsm.js → src/initOnlyAsm.ts} +10 -4
- package/{initOnlyWasm.js → src/initOnlyWasm.ts} +10 -4
- package/{initWasmAsm.js → src/initWasmAsm.ts} +10 -4
- package/src/lib.rs +24 -0
- package/src/mod.ts +4 -0
- package/{packageDetect.js → src/packageDetect.ts} +8 -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 +37 -0
- package/test/jest.spec.ts +24 -0
- package/test/loader-build.js +39 -0
- package/test/wasm.js +8 -0
- package/tsconfig.build.json +19 -0
- package/tsconfig.spec.json +16 -0
- package/LICENSE +0 -201
- package/bundle-polkadot-wasm-crypto.js +0 -661
- package/bundle.js +0 -165
- package/cjs/bundle.d.ts +0 -37
- package/cjs/bundle.js +0 -171
- package/cjs/index.js +0 -5
- package/cjs/init.js +0 -21
- package/cjs/initNone.js +0 -20
- package/cjs/initOnlyAsm.js +0 -20
- package/cjs/initOnlyWasm.js +0 -20
- package/cjs/initWasmAsm.js +0 -20
- package/cjs/package.json +0 -3
- package/cjs/packageDetect.js +0 -10
- package/cjs/packageInfo.js +0 -4
- package/index.d.ts +0 -2
- package/index.js +0 -2
- package/init.js +0 -17
- package/initNone.d.ts +0 -10
- package/initOnlyAsm.d.ts +0 -10
- package/initOnlyWasm.d.ts +0 -10
- package/initWasmAsm.d.ts +0 -10
- package/packageDetect.d.ts +0 -1
- package/packageInfo.d.ts +0 -6
- package/packageInfo.js +0 -1
- /package/{bundle.d.ts → build/bundle.d.ts} +0 -0
- /package/{cjs → build}/index.d.ts +0 -0
- /package/{cjs → build}/init.d.ts +0 -0
- /package/{cjs → build}/initNone.d.ts +0 -0
- /package/{cjs → build}/initOnlyAsm.d.ts +0 -0
- /package/{cjs → build}/initOnlyWasm.d.ts +0 -0
- /package/{cjs → build}/initWasmAsm.d.ts +0 -0
- /package/{cjs → build}/packageDetect.d.ts +0 -0
- /package/{cjs → build}/packageInfo.d.ts +0 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Copyright 2019-2025 @pezkuwi/wasm-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
use secp256k1::{
|
|
5
|
+
ecdsa::{RecoverableSignature, RecoveryId},
|
|
6
|
+
Message, PublicKey, SecretKey, SECP256K1,
|
|
7
|
+
};
|
|
8
|
+
use wasm_bindgen::prelude::*;
|
|
9
|
+
|
|
10
|
+
#[wasm_bindgen]
|
|
11
|
+
pub fn ext_secp_pub_compress(pubkey: &[u8]) -> Vec<u8> {
|
|
12
|
+
match PublicKey::from_slice(&pubkey) {
|
|
13
|
+
Ok(p) => p.serialize().to_vec(),
|
|
14
|
+
_ => panic!("Invalid pubkey provided."),
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#[wasm_bindgen]
|
|
19
|
+
pub fn ext_secp_pub_expand(pubkey: &[u8]) -> Vec<u8> {
|
|
20
|
+
match PublicKey::from_slice(&pubkey) {
|
|
21
|
+
Ok(p) => p.serialize_uncompressed().to_vec(),
|
|
22
|
+
_ => panic!("Invalid pubkey provided."),
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#[wasm_bindgen]
|
|
27
|
+
pub fn ext_secp_from_seed(seed: &[u8]) -> Vec<u8> {
|
|
28
|
+
match SecretKey::from_slice(seed) {
|
|
29
|
+
Ok(s) => {
|
|
30
|
+
let mut res = vec![];
|
|
31
|
+
let pubkey = PublicKey::from_secret_key(SECP256K1, &s);
|
|
32
|
+
|
|
33
|
+
res.extend_from_slice(&s.serialize_secret());
|
|
34
|
+
res.extend_from_slice(&pubkey.serialize());
|
|
35
|
+
|
|
36
|
+
res
|
|
37
|
+
}
|
|
38
|
+
_ => panic!("Invalid seed provided."),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#[wasm_bindgen]
|
|
43
|
+
pub fn ext_secp_recover(hash: &[u8], sig: &[u8], rec: i32) -> Vec<u8> {
|
|
44
|
+
match RecoveryId::from_i32(rec) {
|
|
45
|
+
Ok(r) => match (
|
|
46
|
+
Message::from_slice(hash),
|
|
47
|
+
RecoverableSignature::from_compact(&sig, r),
|
|
48
|
+
) {
|
|
49
|
+
(Ok(m), Ok(s)) => {
|
|
50
|
+
let standard = s.to_standard();
|
|
51
|
+
let mut normalized = standard;
|
|
52
|
+
normalized.normalize_s();
|
|
53
|
+
|
|
54
|
+
// check if the signature is normalized
|
|
55
|
+
if normalized.ne(&standard) {
|
|
56
|
+
panic!("Non-normalized signature provided.");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
match s.recover(&m) {
|
|
60
|
+
Ok(k) => k.serialize().to_vec(),
|
|
61
|
+
_ => panic!("Unable to recover."),
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
_ => panic!("Invalid signature provided."),
|
|
65
|
+
},
|
|
66
|
+
_ => panic!("Invalid recovery data provided."),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[wasm_bindgen]
|
|
71
|
+
pub fn ext_secp_sign(hash: &[u8], seckey: &[u8]) -> Vec<u8> {
|
|
72
|
+
match (Message::from_slice(hash), SecretKey::from_slice(seckey)) {
|
|
73
|
+
(Ok(m), Ok(s)) => {
|
|
74
|
+
let mut res = vec![];
|
|
75
|
+
let (rec, sig) = SECP256K1.sign_ecdsa_recoverable(&m, &s).serialize_compact();
|
|
76
|
+
|
|
77
|
+
res.extend_from_slice(&sig);
|
|
78
|
+
res.push(rec.to_i32() as u8);
|
|
79
|
+
|
|
80
|
+
res
|
|
81
|
+
}
|
|
82
|
+
_ => panic!("Invalid message or secret provided."),
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#[cfg(test)]
|
|
87
|
+
pub mod tests {
|
|
88
|
+
use super::*;
|
|
89
|
+
use hex_literal::hex;
|
|
90
|
+
|
|
91
|
+
#[test]
|
|
92
|
+
fn can_create_pair() {
|
|
93
|
+
let seckey = hex!("4380de832af797688026ce24f85204d508243f201650c1a134929e5458b7fbae");
|
|
94
|
+
let expected = hex!("4380de832af797688026ce24f85204d508243f201650c1a134929e5458b7fbae03fd8c74f795ced92064b86191cb2772b1e3a0947740aa0a5a6e379592471fd85b");
|
|
95
|
+
let res = ext_secp_from_seed(&seckey);
|
|
96
|
+
|
|
97
|
+
assert_eq!(res[..], expected[..]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#[test]
|
|
101
|
+
fn can_pub_compress_full() {
|
|
102
|
+
let pubkey = hex!("04b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af");
|
|
103
|
+
let expected = hex!("03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077");
|
|
104
|
+
let res = ext_secp_pub_compress(&pubkey);
|
|
105
|
+
|
|
106
|
+
assert_eq!(res[..], expected[..]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#[test]
|
|
110
|
+
fn can_pub_expand_comp() {
|
|
111
|
+
let pubkey = hex!("03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077");
|
|
112
|
+
let expected = hex!("04b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af");
|
|
113
|
+
let res = ext_secp_pub_expand(&pubkey);
|
|
114
|
+
|
|
115
|
+
assert_eq!(res[..], expected[..]);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#[test]
|
|
119
|
+
fn can_recover() {
|
|
120
|
+
let expected = hex!("028d13da15a02f3a70677339d51b14177ee9b49657662b35e56a9d9dee17db1d30");
|
|
121
|
+
let sig = hex!("7505f2880114da51b3f5d535f8687953c0ab9af4ab81e592eaebebf53b728d2b6dfd9b5bcd70fee412b1f31360e7c2774009305cb84fc50c1d0ff8034dfa5fff");
|
|
122
|
+
let msg = hex!("a30b64ce1eedf409c8afb801d72c05234e64849ea538c15dd3c8cf4ffcf166c9");
|
|
123
|
+
let res = ext_secp_recover(&msg, &sig, 0);
|
|
124
|
+
|
|
125
|
+
assert_eq!(res[..], expected[..]);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#[test]
|
|
129
|
+
fn can_sign() {
|
|
130
|
+
// JS expectation - doesn't match?
|
|
131
|
+
// let expected = hex!("df92f73d9f060cefacf187b5414491cb992998ace017fa48839b5cda3e264ba8c4efa521361678d9b8582744d77aa4b8d886d7380b7808a683174afad9c4700300");
|
|
132
|
+
let expected = hex!("df92f73d9f060cefacf187b5414491cb992998ace017fa48839b5cda3e264ba83b105adec9e9872647a7d8bb28855b45e22805aea3d097953cbb1391f671d13e01");
|
|
133
|
+
let seckey = hex!("4380de832af797688026ce24f85204d508243f201650c1a134929e5458b7fbae");
|
|
134
|
+
let msg = hex!("68c731589a583d08b70861683b59ce3dd56284cb2f0da5b6cd83e6641dac3aab");
|
|
135
|
+
let res = ext_secp_sign(&msg, &seckey);
|
|
136
|
+
|
|
137
|
+
assert_eq!(res[..], expected[..]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#[test]
|
|
141
|
+
#[should_panic(expected = "Non-normalized signature provided.")]
|
|
142
|
+
fn rejects_high_s_signature() {
|
|
143
|
+
// valid ECDSA signature with a high S value (non-normalized)
|
|
144
|
+
let sig = hex!(
|
|
145
|
+
"fa1b7a71500322d70bcd5b982c678c96c95f68893063e1621b8b85c867b80b1caae8c7c4f8b5c6d181e4c8898a3f4c329da0e2c21f6f80552a660e6c1d9fa201");
|
|
146
|
+
let msg = hex!("9a3c9be3a14a94df8cda1c474c0e9f8b1b35b1d85d2122cb87e6eb3c518e3be3");
|
|
147
|
+
|
|
148
|
+
let _ = ext_secp_recover(&msg, &sig, 0);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -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
|
+
}
|