@nuggetslife/vc 0.0.6 → 0.0.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 +1 -0
- package/index.d.ts +95 -0
- package/index.js +3 -1
- package/package.json +2 -2
- package/src/lib.rs +339 -27
- package/src/types.rs +187 -0
- package/src/validators.rs +68 -0
- package/test.mjs +78 -0
package/Cargo.toml
CHANGED
|
@@ -10,6 +10,7 @@ crate-type = ["cdylib"]
|
|
|
10
10
|
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
|
|
11
11
|
base64 = "0.22.1"
|
|
12
12
|
bs58 = "0.5.1"
|
|
13
|
+
hex = "0.4.3"
|
|
13
14
|
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
|
|
14
15
|
napi = { version = "2.12.2", default-features = false, features = ["napi4", "tokio_rt"] }
|
|
15
16
|
napi-derive = "2.12.2"
|
package/index.d.ts
CHANGED
|
@@ -9,11 +9,91 @@ export interface KeyPairOptions {
|
|
|
9
9
|
privateKeyBase58?: string
|
|
10
10
|
publicKeyBase58?: string
|
|
11
11
|
}
|
|
12
|
+
export interface JwkKeyPairOptions {
|
|
13
|
+
id?: string
|
|
14
|
+
controller?: string
|
|
15
|
+
privateKeyJwk?: JsonWebKey
|
|
16
|
+
publicKeyJwk?: JsonWebKey
|
|
17
|
+
}
|
|
18
|
+
export interface FingerPrintFromPublicKeyOptions {
|
|
19
|
+
publicKeyBase58: string
|
|
20
|
+
}
|
|
21
|
+
export interface KeyPairFromFingerPrintOptions {
|
|
22
|
+
id?: string
|
|
23
|
+
controller?: string
|
|
24
|
+
fingerprint: string
|
|
25
|
+
}
|
|
26
|
+
export interface JsonWebKey {
|
|
27
|
+
/**
|
|
28
|
+
* Indicates the key type used
|
|
29
|
+
* For BLS12381_G1 and BLS12381_G2 the string "EC" MUST be used
|
|
30
|
+
* @see https://tools.ietf.org/html/rfc7517#section-4.1
|
|
31
|
+
*
|
|
32
|
+
*/
|
|
33
|
+
kty: string
|
|
34
|
+
/**
|
|
35
|
+
* Indicates the curve this key is associated with
|
|
36
|
+
* In the case of BLS12-381, the curve will also indicate if it's a G1 or G2 point
|
|
37
|
+
* For a G1 point, use the string "BLS12381_G1"
|
|
38
|
+
* For a G2 point, use the string "BLS12381_G2"
|
|
39
|
+
*/
|
|
40
|
+
crv: string
|
|
41
|
+
/**
|
|
42
|
+
* This is a compression of the public key point
|
|
43
|
+
* For a G1 public key, X is a 384bit base64url encoding of the octet string representation of the coordinate
|
|
44
|
+
* For a G2 public key, X is a 768bit made up of the concatenation of two 384 bit x coordinates known as
|
|
45
|
+
* x_a and x_b in the following form (x_a || x_b) as a base64url encoding of the octet string representation of the two coordinates
|
|
46
|
+
*
|
|
47
|
+
*/
|
|
48
|
+
x: string
|
|
49
|
+
/** @see https://tools.ietf.org/html/rfc7517#section-4.2 */
|
|
50
|
+
use?: string
|
|
51
|
+
/**
|
|
52
|
+
* @see https://tools.ietf.org/html/rfc7517#section-4.3
|
|
53
|
+
*
|
|
54
|
+
*/
|
|
55
|
+
keyOps?: Array<string>
|
|
56
|
+
/**
|
|
57
|
+
* @see https://tools.ietf.org/html/rfc7517#section-4.4
|
|
58
|
+
*
|
|
59
|
+
*/
|
|
60
|
+
alg?: string
|
|
61
|
+
/**
|
|
62
|
+
* @see https://tools.ietf.org/html/rfc7517#section-4.5
|
|
63
|
+
* TODO: Add note about referencing did-jose-extensions when ready
|
|
64
|
+
*
|
|
65
|
+
*/
|
|
66
|
+
kid?: string
|
|
67
|
+
/**
|
|
68
|
+
* IMPORTANT NOTE: d represents the private key value and should not be shared
|
|
69
|
+
* IT IS HIGHLY SENSITIVE DATA AND IF NOT SECURED PROPERLY CONSIDER THE KEY COMPROMISED
|
|
70
|
+
* @see https://tools.ietf.org/html/rfc7517#section-9.2
|
|
71
|
+
*
|
|
72
|
+
*/
|
|
73
|
+
d?: string
|
|
74
|
+
/**
|
|
75
|
+
* This coordinate is not used for BLS Keys, but is kept here to make the interface more standard
|
|
76
|
+
*
|
|
77
|
+
*/
|
|
78
|
+
y?: string
|
|
79
|
+
/**
|
|
80
|
+
* @see https://www.w3.org/TR/WebCryptoAPI/#cryptokey-interface-members
|
|
81
|
+
*
|
|
82
|
+
*/
|
|
83
|
+
ext?: boolean
|
|
84
|
+
}
|
|
12
85
|
export interface GenerateKeyPairOptions {
|
|
13
86
|
id?: string
|
|
14
87
|
controller?: string
|
|
15
88
|
seed?: Buffer
|
|
16
89
|
}
|
|
90
|
+
export interface KeyPairSignerOptions {
|
|
91
|
+
data: Array<Uint8Array>
|
|
92
|
+
}
|
|
93
|
+
export interface KeyPairVerifierOptions {
|
|
94
|
+
data: Array<Uint8Array>
|
|
95
|
+
signature: Uint8Array
|
|
96
|
+
}
|
|
17
97
|
export class Bls12381G2KeyPair {
|
|
18
98
|
id?: string
|
|
19
99
|
controller?: string
|
|
@@ -23,6 +103,21 @@ export class Bls12381G2KeyPair {
|
|
|
23
103
|
constructor(options?: KeyPairOptions | undefined | null)
|
|
24
104
|
static generate(options?: GenerateKeyPairOptions | undefined | null): Promise<Bls12381G2KeyPair>
|
|
25
105
|
static from(options: KeyPairOptions): Promise<Bls12381G2KeyPair>
|
|
106
|
+
static fromJwk(options: JwkKeyPairOptions): Promise<Bls12381G2KeyPair>
|
|
107
|
+
static fromFingerprint(options: KeyPairFromFingerPrintOptions): Promise<Bls12381G2KeyPair>
|
|
26
108
|
get publicKey(): string | null
|
|
109
|
+
publicKeyJwk(): JsonWebKey
|
|
27
110
|
get privateKey(): string | null
|
|
111
|
+
privateKeyJwk(): JsonWebKey
|
|
112
|
+
signer(): KeyPairSigner
|
|
113
|
+
verifier(): KeyPairVerifier
|
|
114
|
+
fingerprint(): string
|
|
115
|
+
static fingerprintFromPublicKey(options: FingerPrintFromPublicKeyOptions): string
|
|
116
|
+
verifyFingerprint(fingerprint: string): void
|
|
117
|
+
}
|
|
118
|
+
export class KeyPairSigner {
|
|
119
|
+
sign(options: KeyPairSignerOptions): Promise<Uint8Array>
|
|
120
|
+
}
|
|
121
|
+
export class KeyPairVerifier {
|
|
122
|
+
verify(options: KeyPairVerifierOptions): Promise<boolean>
|
|
28
123
|
}
|
package/index.js
CHANGED
|
@@ -310,6 +310,8 @@ if (!nativeBinding) {
|
|
|
310
310
|
throw new Error(`Failed to load native binding`)
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
-
const { Bls12381G2KeyPair } = nativeBinding
|
|
313
|
+
const { Bls12381G2KeyPair, KeyPairSigner, KeyPairVerifier } = nativeBinding
|
|
314
314
|
|
|
315
315
|
module.exports.Bls12381G2KeyPair = Bls12381G2KeyPair
|
|
316
|
+
module.exports.KeyPairSigner = KeyPairSigner
|
|
317
|
+
module.exports.KeyPairVerifier = KeyPairVerifier
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuggetslife/vc",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"types": "index.d.ts",
|
|
6
6
|
"napi": {
|
|
@@ -38,6 +38,6 @@
|
|
|
38
38
|
},
|
|
39
39
|
"packageManager": "yarn@4.3.1",
|
|
40
40
|
"optionalDependencies": {
|
|
41
|
-
"@nuggetslife/vc-darwin-arm64": "0.0.
|
|
41
|
+
"@nuggetslife/vc-darwin-arm64": "0.0.8"
|
|
42
42
|
}
|
|
43
43
|
}
|
package/src/lib.rs
CHANGED
|
@@ -3,34 +3,23 @@
|
|
|
3
3
|
#[macro_use]
|
|
4
4
|
extern crate napi_derive;
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
use base64::{
|
|
7
|
+
engine::general_purpose::URL_SAFE, engine::general_purpose::URL_SAFE_NO_PAD, Engine as _,
|
|
8
|
+
};
|
|
7
9
|
use napi::bindgen_prelude::*;
|
|
8
|
-
use
|
|
10
|
+
use types::{
|
|
11
|
+
Bls12381G2KeyPair, BlsCurveName, FingerPrintFromPublicKeyOptions, GenerateKeyPairOptions,
|
|
12
|
+
JsonWebKey, JwkKeyPairOptions, JwkKty, KeyPairFromFingerPrintOptions, KeyPairOptions,
|
|
13
|
+
};
|
|
14
|
+
use validators::{assert_bls_12381_g2_private_jwk, assert_bls_12381_g2_public_jwk};
|
|
15
|
+
use vc::bbs_signatures::bls12381::{
|
|
16
|
+
bls_generate_g2_key, bls_sign, bls_verify, BlsBbsSignRequest, BlsBbsVerifyRequest, BlsKeyPair,
|
|
17
|
+
BLS12381G2_MULTICODEC_IDENTIFIER, DEFAULT_BLS12381_G2_PUBLIC_KEY_LENGTH,
|
|
18
|
+
MULTIBASE_ENCODED_BASE58_IDENTIFIER, VARIABLE_INTEGER_TRAILING_BYTE,
|
|
19
|
+
};
|
|
9
20
|
|
|
10
|
-
|
|
11
|
-
pub
|
|
12
|
-
pub id: Option<String>,
|
|
13
|
-
pub controller: Option<String>,
|
|
14
|
-
pub private_key_buffer: Option<Vec<u8>>,
|
|
15
|
-
pub public_key_buffer: Option<Vec<u8>>,
|
|
16
|
-
#[napi(js_name = "type")]
|
|
17
|
-
pub type_: String,
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
#[napi(object)]
|
|
21
|
-
pub struct KeyPairOptions {
|
|
22
|
-
pub id: Option<String>,
|
|
23
|
-
pub controller: Option<String>,
|
|
24
|
-
pub private_key_base58: Option<String>,
|
|
25
|
-
pub public_key_base58: Option<String>,
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
#[napi(object)]
|
|
29
|
-
pub struct GenerateKeyPairOptions {
|
|
30
|
-
pub id: Option<String>,
|
|
31
|
-
pub controller: Option<String>,
|
|
32
|
-
pub seed: Option<Buffer>,
|
|
33
|
-
}
|
|
21
|
+
pub mod types;
|
|
22
|
+
pub mod validators;
|
|
34
23
|
|
|
35
24
|
#[napi]
|
|
36
25
|
impl Bls12381G2KeyPair {
|
|
@@ -126,6 +115,127 @@ impl Bls12381G2KeyPair {
|
|
|
126
115
|
Bls12381G2KeyPair::new(Some(options)).await
|
|
127
116
|
}
|
|
128
117
|
|
|
118
|
+
#[napi(factory)]
|
|
119
|
+
pub async fn from_jwk(options: JwkKeyPairOptions) -> Result<Bls12381G2KeyPair> {
|
|
120
|
+
let JwkKeyPairOptions {
|
|
121
|
+
id,
|
|
122
|
+
controller,
|
|
123
|
+
private_key_jwk,
|
|
124
|
+
public_key_jwk,
|
|
125
|
+
} = options;
|
|
126
|
+
|
|
127
|
+
if let Some(jwk) = private_key_jwk {
|
|
128
|
+
assert_bls_12381_g2_private_jwk(&jwk)?;
|
|
129
|
+
let public_key_base58 = Some(convert_base64_url_to_base58(jwk.x)?);
|
|
130
|
+
let d = jwk
|
|
131
|
+
.d
|
|
132
|
+
.ok_or(napi::Error::from_reason("jwk.d value is none"))?;
|
|
133
|
+
let private_key_base58 = Some(convert_base64_url_to_base58(d)?);
|
|
134
|
+
|
|
135
|
+
let kp = Bls12381G2KeyPair::new(Some(KeyPairOptions {
|
|
136
|
+
id,
|
|
137
|
+
controller,
|
|
138
|
+
public_key_base58,
|
|
139
|
+
private_key_base58,
|
|
140
|
+
}))
|
|
141
|
+
.await;
|
|
142
|
+
|
|
143
|
+
Ok(kp)
|
|
144
|
+
} else if let Some(jwk) = public_key_jwk {
|
|
145
|
+
assert_bls_12381_g2_public_jwk(&jwk)?;
|
|
146
|
+
let public_key_base58 = Some(convert_base64_url_to_base58(jwk.x)?);
|
|
147
|
+
let kp = Bls12381G2KeyPair::new(Some(KeyPairOptions {
|
|
148
|
+
id,
|
|
149
|
+
controller,
|
|
150
|
+
public_key_base58,
|
|
151
|
+
private_key_base58: None,
|
|
152
|
+
}))
|
|
153
|
+
.await;
|
|
154
|
+
|
|
155
|
+
Ok(kp)
|
|
156
|
+
} else {
|
|
157
|
+
Err(napi::Error::from_reason("The JWK provided is not a valid"))
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#[napi(factory)]
|
|
162
|
+
pub async fn from_fingerprint(
|
|
163
|
+
options: KeyPairFromFingerPrintOptions,
|
|
164
|
+
) -> Result<Bls12381G2KeyPair> {
|
|
165
|
+
let KeyPairFromFingerPrintOptions {
|
|
166
|
+
id,
|
|
167
|
+
controller,
|
|
168
|
+
fingerprint,
|
|
169
|
+
} = options;
|
|
170
|
+
let mut chars = fingerprint.chars();
|
|
171
|
+
let head = chars
|
|
172
|
+
.next()
|
|
173
|
+
.ok_or(napi::Error::from_reason("fingerprint string is empty"))?
|
|
174
|
+
.to_string();
|
|
175
|
+
|
|
176
|
+
if head != MULTIBASE_ENCODED_BASE58_IDENTIFIER {
|
|
177
|
+
return Err(napi::Error::from_reason(
|
|
178
|
+
format!("Unsupported fingerprint type: expected first character to be `z` indicating base58 encoding, received {head}")
|
|
179
|
+
));
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
let rest = chars.collect::<String>();
|
|
183
|
+
let buffer = bs58::decode(rest).into_vec().map_err(|err| {
|
|
184
|
+
napi::Error::from_reason(format!(
|
|
185
|
+
"failed to decode bs58 fingerprint to bytes. error: {err}"
|
|
186
|
+
))
|
|
187
|
+
})?;
|
|
188
|
+
|
|
189
|
+
if buffer.len() != BLS12381G2_MULTICODEC_IDENTIFIER as usize {
|
|
190
|
+
return Err(napi::Error::from_reason(
|
|
191
|
+
format!("Unsupported public key length: expected `${DEFAULT_BLS12381_G2_PUBLIC_KEY_LENGTH}` received {}", buffer.len())
|
|
192
|
+
));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if buffer[0] != DEFAULT_BLS12381_G2_PUBLIC_KEY_LENGTH {
|
|
196
|
+
return Err(napi::Error::from_reason(
|
|
197
|
+
format!("Unsupported public key identifier: expected second character to be {BLS12381G2_MULTICODEC_IDENTIFIER} indicating BLS12381G2 key pair, received {}", buffer[0])
|
|
198
|
+
));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if buffer[1] != VARIABLE_INTEGER_TRAILING_BYTE {
|
|
202
|
+
return Err(napi::Error::from_reason(
|
|
203
|
+
format!("Missing variable integer trailing byte: expected third character to be {BLS12381G2_MULTICODEC_IDENTIFIER} indicating BLS12381G2 key pair, received {}", buffer[1])
|
|
204
|
+
));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let pubkey_base58 = bs58::encode(buffer).into_string();
|
|
208
|
+
let opts = FingerPrintFromPublicKeyOptions {
|
|
209
|
+
public_key_base58: pubkey_base58.clone(),
|
|
210
|
+
};
|
|
211
|
+
let fingerprint = Bls12381G2KeyPair::fingerprint_from_public_key(opts).map_err(|err| {
|
|
212
|
+
napi::Error::from_reason(format!("fingerprint_from_public_key failed. error: {err}"))
|
|
213
|
+
})?;
|
|
214
|
+
let mapped_controller = match controller {
|
|
215
|
+
Some(v) => v,
|
|
216
|
+
None => {
|
|
217
|
+
format!("did:key:{fingerprint}",)
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
let mapped_id = match id {
|
|
222
|
+
Some(v) => v,
|
|
223
|
+
None => {
|
|
224
|
+
format!("#{fingerprint}",)
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
let kp = Bls12381G2KeyPair::new(Some(KeyPairOptions {
|
|
229
|
+
id: Some(mapped_id),
|
|
230
|
+
controller: Some(mapped_controller),
|
|
231
|
+
public_key_base58: Some(pubkey_base58),
|
|
232
|
+
private_key_base58: None,
|
|
233
|
+
}))
|
|
234
|
+
.await;
|
|
235
|
+
|
|
236
|
+
Ok(kp)
|
|
237
|
+
}
|
|
238
|
+
|
|
129
239
|
#[napi(getter)]
|
|
130
240
|
pub fn public_key(&self) -> Option<String> {
|
|
131
241
|
self
|
|
@@ -134,6 +244,26 @@ impl Bls12381G2KeyPair {
|
|
|
134
244
|
.map(|b| bs58::encode(b).into_string())
|
|
135
245
|
}
|
|
136
246
|
|
|
247
|
+
#[napi]
|
|
248
|
+
pub fn public_key_jwk(&self) -> Result<JsonWebKey> {
|
|
249
|
+
let Some(ref public_key_buffer) = self.public_key_buffer else {
|
|
250
|
+
return Err(napi::Error::from_reason("no public_key_buffer"));
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
Ok(JsonWebKey {
|
|
254
|
+
kid: self.id.to_owned(),
|
|
255
|
+
kty: JwkKty::EC.into(),
|
|
256
|
+
crv: BlsCurveName::G2.into(),
|
|
257
|
+
x: URL_SAFE_NO_PAD.encode(public_key_buffer),
|
|
258
|
+
use_: None,
|
|
259
|
+
key_ops: None,
|
|
260
|
+
alg: None,
|
|
261
|
+
d: None,
|
|
262
|
+
y: None,
|
|
263
|
+
ext: None,
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
137
267
|
#[napi(getter)]
|
|
138
268
|
pub fn private_key(&self) -> Option<String> {
|
|
139
269
|
self
|
|
@@ -141,6 +271,188 @@ impl Bls12381G2KeyPair {
|
|
|
141
271
|
.clone()
|
|
142
272
|
.map(|b| bs58::encode(b).into_string())
|
|
143
273
|
}
|
|
274
|
+
|
|
275
|
+
#[napi]
|
|
276
|
+
pub fn private_key_jwk(&self) -> Result<JsonWebKey> {
|
|
277
|
+
let Some(ref public_key_buffer) = self.public_key_buffer else {
|
|
278
|
+
return Err(napi::Error::from_reason("no public_key_buffer"));
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
let Some(ref private_key_buffer) = self.private_key_buffer else {
|
|
282
|
+
return Err(napi::Error::from_reason("no private_key_buffer"));
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
Ok(JsonWebKey {
|
|
286
|
+
kid: self.id.to_owned(),
|
|
287
|
+
kty: JwkKty::EC.into(),
|
|
288
|
+
crv: BlsCurveName::G2.into(),
|
|
289
|
+
x: URL_SAFE_NO_PAD.encode(public_key_buffer),
|
|
290
|
+
d: Some(URL_SAFE_NO_PAD.encode(private_key_buffer)),
|
|
291
|
+
use_: None,
|
|
292
|
+
key_ops: None,
|
|
293
|
+
alg: None,
|
|
294
|
+
y: None,
|
|
295
|
+
ext: None,
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
#[napi]
|
|
300
|
+
pub fn signer(&self) -> KeyPairSigner {
|
|
301
|
+
KeyPairSigner { key: self.clone() }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
#[napi]
|
|
305
|
+
pub fn verifier(&self) -> KeyPairVerifier {
|
|
306
|
+
KeyPairVerifier { key: self.clone() }
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
#[napi]
|
|
310
|
+
pub fn fingerprint(&self) -> Result<String> {
|
|
311
|
+
let Some(public_key_base58) = self.public_key() else {
|
|
312
|
+
return Err(napi::Error::from_reason("no public key"));
|
|
313
|
+
};
|
|
314
|
+
Bls12381G2KeyPair::fingerprint_from_public_key(FingerPrintFromPublicKeyOptions {
|
|
315
|
+
public_key_base58,
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
#[napi]
|
|
320
|
+
pub fn fingerprint_from_public_key(options: FingerPrintFromPublicKeyOptions) -> Result<String> {
|
|
321
|
+
let FingerPrintFromPublicKeyOptions { public_key_base58 } = options;
|
|
322
|
+
let mut key_bytes = bs58::decode(public_key_base58).into_vec().map_err(|err| {
|
|
323
|
+
napi::Error::from_reason(format!(
|
|
324
|
+
"failed to decode public_key_base58 value. error: {err}"
|
|
325
|
+
))
|
|
326
|
+
})?;
|
|
327
|
+
|
|
328
|
+
let mut buffer = vec![
|
|
329
|
+
BLS12381G2_MULTICODEC_IDENTIFIER,
|
|
330
|
+
VARIABLE_INTEGER_TRAILING_BYTE,
|
|
331
|
+
];
|
|
332
|
+
buffer.append(&mut key_bytes);
|
|
333
|
+
|
|
334
|
+
Ok(format!(
|
|
335
|
+
"{}{}",
|
|
336
|
+
MULTIBASE_ENCODED_BASE58_IDENTIFIER,
|
|
337
|
+
bs58::encode(buffer).into_string()
|
|
338
|
+
))
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
#[napi]
|
|
342
|
+
pub fn verify_fingerprint(&self, fingerprint: String) -> Result<()> {
|
|
343
|
+
// fingerprint should have `z` prefix indicating
|
|
344
|
+
// that it's multi-base encoded
|
|
345
|
+
let mut chars = fingerprint.chars();
|
|
346
|
+
let head = chars
|
|
347
|
+
.next()
|
|
348
|
+
.ok_or(napi::Error::from_reason("fingerprint string is empty"))?
|
|
349
|
+
.to_string();
|
|
350
|
+
|
|
351
|
+
let rest = chars.collect::<String>();
|
|
352
|
+
|
|
353
|
+
if head != MULTIBASE_ENCODED_BASE58_IDENTIFIER {
|
|
354
|
+
return Err(napi::Error::from_reason(
|
|
355
|
+
"`fingerprint` must be a multibase encoded string.",
|
|
356
|
+
));
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
let fingerprint_buffer = bs58::decode(rest).into_vec().map_err(|err| {
|
|
360
|
+
napi::Error::from_reason(format!("failed to decode bs58 value: error: {err}"))
|
|
361
|
+
})?;
|
|
362
|
+
|
|
363
|
+
let public_key_buffer = self
|
|
364
|
+
.public_key_buffer
|
|
365
|
+
.clone()
|
|
366
|
+
.ok_or(napi::Error::from_reason("public key buffer is missing"))?;
|
|
367
|
+
|
|
368
|
+
let leader = hex::encode(&fingerprint_buffer[..2]);
|
|
369
|
+
let leader_match = if leader == "eb01" { true } else { false };
|
|
370
|
+
let bytes_match = if &public_key_buffer == &fingerprint_buffer[2..] {
|
|
371
|
+
true
|
|
372
|
+
} else {
|
|
373
|
+
false
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
if leader_match && bytes_match {
|
|
377
|
+
Ok(())
|
|
378
|
+
} else {
|
|
379
|
+
Err(napi::Error::from_reason(
|
|
380
|
+
"The fingerprint does not match the public key",
|
|
381
|
+
))
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
pub fn convert_base64_url_to_base58(value: String) -> Result<String> {
|
|
387
|
+
let decoded = URL_SAFE.decode(value).map_err(|err| {
|
|
388
|
+
napi::Error::from_reason(format!("failed to decode base64 url_safe. error {err}"))
|
|
389
|
+
})?;
|
|
390
|
+
Ok(bs58::encode(decoded).into_string())
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
#[napi]
|
|
394
|
+
pub struct KeyPairSigner {
|
|
395
|
+
key: Bls12381G2KeyPair,
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
#[napi(object)]
|
|
399
|
+
pub struct KeyPairSignerOptions {
|
|
400
|
+
pub data: Vec<Uint8Array>,
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
#[napi]
|
|
404
|
+
impl KeyPairSigner {
|
|
405
|
+
#[napi]
|
|
406
|
+
pub async fn sign(&self, options: KeyPairSignerOptions) -> Result<Uint8Array> {
|
|
407
|
+
if self.key.private_key_buffer.is_none() {
|
|
408
|
+
return Err(napi::Error::from_reason("No private key to sign with."));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
let messages: Vec<Vec<u8>> = options.data.into_iter().map(|x| x.to_vec()).collect();
|
|
412
|
+
let key_pair = BlsKeyPair {
|
|
413
|
+
public_key: self.key.public_key_buffer.clone(),
|
|
414
|
+
secret_key: self.key.private_key_buffer.clone(),
|
|
415
|
+
};
|
|
416
|
+
let sig = bls_sign(BlsBbsSignRequest { messages, key_pair })
|
|
417
|
+
.await
|
|
418
|
+
.map_err(|err| napi::Error::from_reason(format!("bls_signed failed: {err}")))?;
|
|
419
|
+
|
|
420
|
+
Ok(Uint8Array::from(sig))
|
|
421
|
+
}
|
|
144
422
|
}
|
|
145
423
|
|
|
146
|
-
|
|
424
|
+
#[napi]
|
|
425
|
+
pub struct KeyPairVerifier {
|
|
426
|
+
key: Bls12381G2KeyPair,
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
#[napi(object)]
|
|
430
|
+
pub struct KeyPairVerifierOptions {
|
|
431
|
+
pub data: Vec<Uint8Array>,
|
|
432
|
+
pub signature: Uint8Array,
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
#[napi]
|
|
436
|
+
impl KeyPairVerifier {
|
|
437
|
+
#[napi]
|
|
438
|
+
pub async fn verify(&self, options: KeyPairVerifierOptions) -> Result<bool> {
|
|
439
|
+
let KeyPairVerifierOptions { data, signature } = options;
|
|
440
|
+
let Some(public_key) = self.key.public_key_buffer.clone() else {
|
|
441
|
+
return Err(napi::Error::from_reason(
|
|
442
|
+
"key.public_key is required for verify",
|
|
443
|
+
));
|
|
444
|
+
};
|
|
445
|
+
let signature = signature.to_vec();
|
|
446
|
+
let messages = data.into_iter().map(|a| a.to_vec()).collect::<Vec<_>>();
|
|
447
|
+
|
|
448
|
+
let verified = bls_verify(BlsBbsVerifyRequest {
|
|
449
|
+
public_key,
|
|
450
|
+
signature,
|
|
451
|
+
messages,
|
|
452
|
+
})
|
|
453
|
+
.await
|
|
454
|
+
.map_err(|err| napi::Error::from_reason(format!("bls_verify failed: {err}")))?;
|
|
455
|
+
|
|
456
|
+
Ok(verified)
|
|
457
|
+
}
|
|
458
|
+
}
|
package/src/types.rs
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
use napi::bindgen_prelude::*;
|
|
2
|
+
|
|
3
|
+
#[derive(Clone)]
|
|
4
|
+
#[napi]
|
|
5
|
+
pub struct Bls12381G2KeyPair {
|
|
6
|
+
pub id: Option<String>,
|
|
7
|
+
pub controller: Option<String>,
|
|
8
|
+
pub private_key_buffer: Option<Vec<u8>>,
|
|
9
|
+
pub public_key_buffer: Option<Vec<u8>>,
|
|
10
|
+
#[napi(js_name = "type")]
|
|
11
|
+
pub type_: String,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#[napi(object)]
|
|
15
|
+
pub struct KeyPairOptions {
|
|
16
|
+
pub id: Option<String>,
|
|
17
|
+
pub controller: Option<String>,
|
|
18
|
+
pub private_key_base58: Option<String>,
|
|
19
|
+
pub public_key_base58: Option<String>,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#[napi(object)]
|
|
23
|
+
pub struct JwkKeyPairOptions {
|
|
24
|
+
pub id: Option<String>,
|
|
25
|
+
pub controller: Option<String>,
|
|
26
|
+
pub private_key_jwk: Option<JsonWebKey>,
|
|
27
|
+
pub public_key_jwk: Option<JsonWebKey>,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#[napi(object)]
|
|
31
|
+
pub struct FingerPrintFromPublicKeyOptions {
|
|
32
|
+
pub public_key_base58: String,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#[napi(object)]
|
|
36
|
+
pub struct KeyPairFromFingerPrintOptions {
|
|
37
|
+
pub id: Option<String>,
|
|
38
|
+
pub controller: Option<String>,
|
|
39
|
+
pub fingerprint: String,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#[napi(object)]
|
|
43
|
+
pub struct JsonWebKey {
|
|
44
|
+
/////
|
|
45
|
+
/// Indicates the key type used
|
|
46
|
+
/// For BLS12381_G1 and BLS12381_G2 the string "EC" MUST be used
|
|
47
|
+
//
|
|
48
|
+
/// @see https://tools.ietf.org/html/rfc7517#section-4.1
|
|
49
|
+
///
|
|
50
|
+
pub kty: String,
|
|
51
|
+
|
|
52
|
+
/////
|
|
53
|
+
/// Indicates the curve this key is associated with
|
|
54
|
+
/// In the case of BLS12-381, the curve will also indicate if it's a G1 or G2 point
|
|
55
|
+
//
|
|
56
|
+
/// For a G1 point, use the string "BLS12381_G1"
|
|
57
|
+
/// For a G2 point, use the string "BLS12381_G2"
|
|
58
|
+
//
|
|
59
|
+
pub crv: String,
|
|
60
|
+
|
|
61
|
+
/////
|
|
62
|
+
/// This is a compression of the public key point
|
|
63
|
+
//
|
|
64
|
+
/// For a G1 public key, X is a 384bit base64url encoding of the octet string representation of the coordinate
|
|
65
|
+
/// For a G2 public key, X is a 768bit made up of the concatenation of two 384 bit x coordinates known as
|
|
66
|
+
/// x_a and x_b in the following form (x_a || x_b) as a base64url encoding of the octet string representation of the two coordinates
|
|
67
|
+
///
|
|
68
|
+
pub x: String,
|
|
69
|
+
|
|
70
|
+
/////
|
|
71
|
+
/// @see https://tools.ietf.org/html/rfc7517#section-4.2
|
|
72
|
+
//
|
|
73
|
+
#[napi(js_name = "use")]
|
|
74
|
+
pub use_: Option<String>,
|
|
75
|
+
|
|
76
|
+
/////
|
|
77
|
+
/// @see https://tools.ietf.org/html/rfc7517#section-4.3
|
|
78
|
+
///
|
|
79
|
+
pub key_ops: Option<Vec<String>>,
|
|
80
|
+
|
|
81
|
+
/////
|
|
82
|
+
/// @see https://tools.ietf.org/html/rfc7517#section-4.4
|
|
83
|
+
///
|
|
84
|
+
pub alg: Option<String>,
|
|
85
|
+
|
|
86
|
+
/////
|
|
87
|
+
/// @see https://tools.ietf.org/html/rfc7517#section-4.5
|
|
88
|
+
//
|
|
89
|
+
/// TODO: Add note about referencing did-jose-extensions when ready
|
|
90
|
+
///
|
|
91
|
+
pub kid: Option<String>,
|
|
92
|
+
|
|
93
|
+
/////
|
|
94
|
+
//
|
|
95
|
+
/// IMPORTANT NOTE: d represents the private key value and should not be shared
|
|
96
|
+
/// IT IS HIGHLY SENSITIVE DATA AND IF NOT SECURED PROPERLY CONSIDER THE KEY COMPROMISED
|
|
97
|
+
//
|
|
98
|
+
/// @see https://tools.ietf.org/html/rfc7517#section-9.2
|
|
99
|
+
///
|
|
100
|
+
pub d: Option<String>,
|
|
101
|
+
|
|
102
|
+
/////
|
|
103
|
+
/// This coordinate is not used for BLS Keys, but is kept here to make the interface more standard
|
|
104
|
+
///
|
|
105
|
+
pub y: Option<String>,
|
|
106
|
+
|
|
107
|
+
/////
|
|
108
|
+
/// @see https://www.w3.org/TR/WebCryptoAPI/#cryptokey-interface-members
|
|
109
|
+
///
|
|
110
|
+
pub ext: Option<bool>,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
#[napi(object)]
|
|
114
|
+
pub struct GenerateKeyPairOptions {
|
|
115
|
+
pub id: Option<String>,
|
|
116
|
+
pub controller: Option<String>,
|
|
117
|
+
pub seed: Option<Buffer>,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#[derive(PartialEq)]
|
|
121
|
+
pub enum BlsCurveName {
|
|
122
|
+
DeprecatedG1,
|
|
123
|
+
DeprecatedG2,
|
|
124
|
+
G1,
|
|
125
|
+
G2,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
impl TryFrom<&str> for BlsCurveName {
|
|
129
|
+
type Error = napi::Error;
|
|
130
|
+
|
|
131
|
+
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
|
|
132
|
+
match value {
|
|
133
|
+
"BLS12381_G1" => Ok(BlsCurveName::DeprecatedG1),
|
|
134
|
+
"BLS12381_G2" => Ok(BlsCurveName::DeprecatedG2),
|
|
135
|
+
"Bls12381G1" => Ok(BlsCurveName::G1),
|
|
136
|
+
"Bls12381G2" => Ok(BlsCurveName::G2),
|
|
137
|
+
_ => Err(napi::Error::from_reason(format!(
|
|
138
|
+
"no matching BlsNamedCurve for value: {}",
|
|
139
|
+
value
|
|
140
|
+
))),
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
impl Into<String> for BlsCurveName {
|
|
146
|
+
fn into(self) -> String {
|
|
147
|
+
match self {
|
|
148
|
+
BlsCurveName::DeprecatedG1 => String::from("BLS12381_G1"),
|
|
149
|
+
BlsCurveName::DeprecatedG2 => String::from("BLS12381_G2"),
|
|
150
|
+
BlsCurveName::G1 => String::from("Bls12381G1"),
|
|
151
|
+
BlsCurveName::G2 => String::from("Bls12381G2"),
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#[derive(PartialEq)]
|
|
157
|
+
pub enum JwkKty {
|
|
158
|
+
OctetKeyPair,
|
|
159
|
+
EC,
|
|
160
|
+
RSA,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
impl TryFrom<&str> for JwkKty {
|
|
164
|
+
type Error = napi::Error;
|
|
165
|
+
|
|
166
|
+
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
|
|
167
|
+
match value {
|
|
168
|
+
"OKP" => Ok(JwkKty::OctetKeyPair),
|
|
169
|
+
"EC" => Ok(JwkKty::EC),
|
|
170
|
+
"RSA" => Ok(JwkKty::RSA),
|
|
171
|
+
_ => Err(napi::Error::from_reason(format!(
|
|
172
|
+
"no matching JwkKty for value: {}",
|
|
173
|
+
value
|
|
174
|
+
))),
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
impl Into<String> for JwkKty {
|
|
180
|
+
fn into(self) -> String {
|
|
181
|
+
match self {
|
|
182
|
+
JwkKty::OctetKeyPair => String::from("OKP"),
|
|
183
|
+
JwkKty::EC => String::from("EC"),
|
|
184
|
+
JwkKty::RSA => String::from("RSA"),
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
use crate::types::{BlsCurveName, JsonWebKey, JwkKty};
|
|
2
|
+
|
|
3
|
+
pub fn check_common_bls_jwk_values(jwk: &JsonWebKey) -> napi::Result<()> {
|
|
4
|
+
BlsCurveName::try_from(jwk.crv.as_str())?;
|
|
5
|
+
let kty = JwkKty::try_from(jwk.kty.as_str())?;
|
|
6
|
+
if kty == JwkKty::EC || kty == JwkKty::OctetKeyPair {
|
|
7
|
+
Ok(())
|
|
8
|
+
} else {
|
|
9
|
+
Err(napi::Error::from_reason(format!(
|
|
10
|
+
"kty value: {} not allowed",
|
|
11
|
+
jwk.kty
|
|
12
|
+
)))
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pub fn assert_public_bls_jwk(jwk: &JsonWebKey) -> napi::Result<()> {
|
|
17
|
+
check_common_bls_jwk_values(jwk)?;
|
|
18
|
+
if jwk.d.is_none() {
|
|
19
|
+
Ok(())
|
|
20
|
+
} else {
|
|
21
|
+
Err(napi::Error::from_reason(
|
|
22
|
+
"expected jwk.d to be none, found some value",
|
|
23
|
+
))
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pub fn assert_private_bls_jwk(jwk: &JsonWebKey) -> napi::Result<()> {
|
|
28
|
+
check_common_bls_jwk_values(jwk)?;
|
|
29
|
+
Ok(())
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pub fn assert_bls_12381_g2_public_jwk(jwk: &JsonWebKey) -> napi::Result<()> {
|
|
33
|
+
let curve = BlsCurveName::try_from(jwk.crv.as_str())?;
|
|
34
|
+
if curve != BlsCurveName::DeprecatedG2 || curve != BlsCurveName::G2 {
|
|
35
|
+
return Err(napi::Error::from_reason(format!(
|
|
36
|
+
"curve value: {} not allowed",
|
|
37
|
+
jwk.crv
|
|
38
|
+
)));
|
|
39
|
+
}
|
|
40
|
+
assert_public_bls_jwk(jwk)?;
|
|
41
|
+
if jwk.x.len() != 128 {
|
|
42
|
+
Err(napi::Error::from_reason(format!(
|
|
43
|
+
"jwk.x expected length 128, got {}",
|
|
44
|
+
jwk.x.len()
|
|
45
|
+
)))
|
|
46
|
+
} else {
|
|
47
|
+
Ok(())
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pub fn assert_bls_12381_g2_private_jwk(jwk: &JsonWebKey) -> napi::Result<()> {
|
|
52
|
+
let curve = BlsCurveName::try_from(jwk.crv.as_str())?;
|
|
53
|
+
if curve != BlsCurveName::DeprecatedG2 || curve != BlsCurveName::G2 {
|
|
54
|
+
return Err(napi::Error::from_reason(format!(
|
|
55
|
+
"curve value: {} not allowed",
|
|
56
|
+
jwk.crv
|
|
57
|
+
)));
|
|
58
|
+
}
|
|
59
|
+
assert_private_bls_jwk(jwk)?;
|
|
60
|
+
if jwk.x.len() != 128 {
|
|
61
|
+
Err(napi::Error::from_reason(format!(
|
|
62
|
+
"jwk.x expected length 128, got {}",
|
|
63
|
+
jwk.x.len()
|
|
64
|
+
)))
|
|
65
|
+
} else {
|
|
66
|
+
Ok(())
|
|
67
|
+
}
|
|
68
|
+
}
|
package/test.mjs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { Bls12381G2KeyPair } from '@mattrglobal/bls12381-key-pair';
|
|
4
|
+
import { Bls12381G2KeyPair as NewKeyPairClass } from './index.js'
|
|
5
|
+
import bs58 from 'bs58'
|
|
6
|
+
import bbs from '@nuggetslife/ffi-bbs-signatures'
|
|
7
|
+
|
|
8
|
+
const seed = Buffer.alloc(32, 0)
|
|
9
|
+
const address = '0x581510277Bc56802dE75BA021b66873437e0169f'
|
|
10
|
+
const addressBase58 = bs58.encode(Buffer.from(address.slice(2), 'hex'))
|
|
11
|
+
|
|
12
|
+
//Set of messages we wish to sign
|
|
13
|
+
const messages = [
|
|
14
|
+
Uint8Array.from(Buffer.from("message1", "utf8")),
|
|
15
|
+
Uint8Array.from(Buffer.from("message2", "utf8")),
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
test('gen keypair for same seed', async () => {
|
|
19
|
+
const mattr = await Bls12381G2KeyPair.generate({ seed, });
|
|
20
|
+
const harry = await NewKeyPairClass.generate({ seed })
|
|
21
|
+
const ffi = await bbs.generateBls12381G2KeyPair(seed)
|
|
22
|
+
const ffi_sec_key = bs58.encode(Buffer.from(ffi.secretKey))
|
|
23
|
+
const ffi_pub_key = bs58.encode(Buffer.from(ffi.publicKey))
|
|
24
|
+
|
|
25
|
+
// private keys match
|
|
26
|
+
assert.equal(mattr.privateKey, harry.privateKey)
|
|
27
|
+
assert.equal(ffi_sec_key, harry.privateKey)
|
|
28
|
+
|
|
29
|
+
// public keys match
|
|
30
|
+
assert.equal(mattr.publicKey, harry.publicKey)
|
|
31
|
+
assert.equal(ffi_pub_key, harry.publicKey)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
test('keypair from keypair', async () => {
|
|
36
|
+
const mattr = await Bls12381G2KeyPair.generate({ seed, });
|
|
37
|
+
const harry = await NewKeyPairClass.from({
|
|
38
|
+
id: mattr.id,
|
|
39
|
+
controller: mattr.controller,
|
|
40
|
+
publicKeyBase58: mattr.publicKey,
|
|
41
|
+
privateKeyBase58: mattr.privateKey
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// private keys match
|
|
45
|
+
assert.equal(mattr.id, harry.id)
|
|
46
|
+
assert.equal(mattr.controller, harry.controller)
|
|
47
|
+
assert.equal(mattr.publicKey, harry.publicKey)
|
|
48
|
+
assert.equal(mattr.privateKey, harry.privateKey)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
test('sign and verify', async () => {
|
|
53
|
+
const mattr = await Bls12381G2KeyPair.generate({ seed, });
|
|
54
|
+
const harry = await NewKeyPairClass.generate({ seed })
|
|
55
|
+
|
|
56
|
+
const ms = mattr.signer();
|
|
57
|
+
const hs = harry.signer();
|
|
58
|
+
|
|
59
|
+
const sign_opts = { data: messages }
|
|
60
|
+
|
|
61
|
+
let ms_sig = await ms.sign(sign_opts);
|
|
62
|
+
let hs_sig = await hs.sign(sign_opts);
|
|
63
|
+
|
|
64
|
+
assert.equal(ms_sig.length, hs_sig.length)
|
|
65
|
+
|
|
66
|
+
const hsv = harry.verifier();
|
|
67
|
+
const msv = mattr.verifier();
|
|
68
|
+
|
|
69
|
+
let hs_verify_hs_sig = await hsv.verify({ data: messages, signature: hs_sig })
|
|
70
|
+
let hs_verify_ms_sig = await hsv.verify({ data: messages, signature: ms_sig })
|
|
71
|
+
let ms_verify_ms_sig = await msv.verify({ data: messages, signature: ms_sig })
|
|
72
|
+
let ms_verify_hs_sig = await msv.verify({ data: messages, signature: hs_sig })
|
|
73
|
+
|
|
74
|
+
assert.equal(hs_verify_hs_sig, true)
|
|
75
|
+
assert.equal(hs_verify_ms_sig, true)
|
|
76
|
+
assert.equal(ms_verify_ms_sig, true)
|
|
77
|
+
assert.equal(ms_verify_hs_sig, true)
|
|
78
|
+
})
|