@road-labs/ocmf-crypto-noble 0.0.1
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/build/cjs/crypto.js +25 -0
- package/build/cjs/curve.js +72 -0
- package/build/cjs/index.js +28 -0
- package/build/cjs/private-key.js +44 -0
- package/build/cjs/public-key.js +52 -0
- package/build/cjs/sign.js +17 -0
- package/build/cjs/verify.js +15 -0
- package/build/es2022/crypto.js +18 -0
- package/build/es2022/curve.js +69 -0
- package/build/es2022/index.js +6 -0
- package/build/es2022/private-key.js +40 -0
- package/build/es2022/public-key.js +48 -0
- package/build/es2022/sign.js +14 -0
- package/build/es2022/verify.js +12 -0
- package/build/types/crypto.d.ts +9 -0
- package/build/types/curve.d.ts +3 -0
- package/build/types/index.d.ts +6 -0
- package/build/types/private-key.d.ts +16 -0
- package/build/types/public-key.d.ts +10 -0
- package/build/types/sign.d.ts +10 -0
- package/build/types/verify.d.ts +10 -0
- package/jest.config.js +11 -0
- package/package.json +37 -0
- package/src/crypto.ts +46 -0
- package/src/curve.ts +101 -0
- package/src/index.ts +6 -0
- package/src/private-key.ts +60 -0
- package/src/public-key.ts +67 -0
- package/src/sign.ts +36 -0
- package/src/verify.ts +40 -0
- package/test/private-key.spec.ts +40 -0
- package/test/public-key.spec.ts +39 -0
- package/test/sign.spec.ts +26 -0
- package/test/verify.spec.ts +26 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Crypto = void 0;
|
|
7
|
+
const private_key_1 = require("./private-key");
|
|
8
|
+
const public_key_1 = require("./public-key");
|
|
9
|
+
const sign_1 = __importDefault(require("./sign"));
|
|
10
|
+
const verify_1 = __importDefault(require("./verify"));
|
|
11
|
+
class Crypto {
|
|
12
|
+
async decodeEcPrivateKey(value, format) {
|
|
13
|
+
return private_key_1.EcPrivateKey.fromEncoded(value, format);
|
|
14
|
+
}
|
|
15
|
+
async decodeEcPublicKey(value, format) {
|
|
16
|
+
return public_key_1.EcPublicKey.fromEncoded(value, format);
|
|
17
|
+
}
|
|
18
|
+
async sign(data, privateKey, hash, format) {
|
|
19
|
+
return (0, sign_1.default)(data, privateKey, hash, format);
|
|
20
|
+
}
|
|
21
|
+
async verify(signature, data, publicKey, hash, format) {
|
|
22
|
+
return (0, verify_1.default)(signature, data, publicKey, hash, format);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.Crypto = Crypto;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveCurveFn = resolveCurveFn;
|
|
4
|
+
const secp256k1_1 = require("@noble/curves/secp256k1");
|
|
5
|
+
const nist_1 = require("@noble/curves/nist");
|
|
6
|
+
const modular_1 = require("@noble/curves/abstract/modular");
|
|
7
|
+
const sha2_1 = require("@noble/hashes/sha2");
|
|
8
|
+
const weierstrass_1 = require("@noble/curves/abstract/weierstrass");
|
|
9
|
+
const secp192r1 = (0, weierstrass_1.weierstrass)({
|
|
10
|
+
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffc'),
|
|
11
|
+
b: BigInt('0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1'),
|
|
12
|
+
Fp: (0, modular_1.Field)(BigInt('0xfffffffffffffffffffffffffffffffeffffffffffffffff')),
|
|
13
|
+
n: BigInt('0xffffffffffffffffffffffff99def836146bc9b1b4d22831'),
|
|
14
|
+
Gx: BigInt('0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012'),
|
|
15
|
+
Gy: BigInt('0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811'),
|
|
16
|
+
h: BigInt(1),
|
|
17
|
+
lowS: false,
|
|
18
|
+
hash: sha2_1.sha256,
|
|
19
|
+
});
|
|
20
|
+
const secp192k1 = (0, weierstrass_1.weierstrass)({
|
|
21
|
+
a: BigInt(0),
|
|
22
|
+
b: BigInt(3),
|
|
23
|
+
Fp: (0, modular_1.Field)(BigInt('0xfffffffffffffffffffffffffffffffffffffffeffffee37')),
|
|
24
|
+
n: BigInt('0xfffffffffffffffffffffffe26f2fc170f69466a74defd8d'),
|
|
25
|
+
Gx: BigInt('0xdb4ff10ec057e9ae26b07d0280b7f4341da5d1b1eae06c7d'),
|
|
26
|
+
Gy: BigInt('0x9b2f2f6d9c5628a7844163d015be86344082aa88d95e2f9d'),
|
|
27
|
+
h: BigInt(1),
|
|
28
|
+
lowS: false,
|
|
29
|
+
hash: sha2_1.sha256,
|
|
30
|
+
});
|
|
31
|
+
const brainpool256r1 = (0, weierstrass_1.weierstrass)({
|
|
32
|
+
a: BigInt('0x7d5a0975fc2c3057eef67530417affe7fb8055c126dc5c6ce94a4b44f330b5d9'),
|
|
33
|
+
b: BigInt('0x26dc5c6ce94a4b44f330b5d9bbd77cbf958416295cf7e1ce6bccdc18ff8c07b6'),
|
|
34
|
+
Fp: (0, modular_1.Field)(BigInt('0xa9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5377')),
|
|
35
|
+
n: BigInt('0xa9fb57dba1eea9bc3e660a909d838d718c397aa3b561a6f7901e0e82974856a7'),
|
|
36
|
+
Gx: BigInt('0x8bd2aeb9cb7e57cb2c4b482ffc81b7afb9de27e1e3bd23c23a4453bd9ace3262'),
|
|
37
|
+
Gy: BigInt('0x547ef835c3dac4fd97f8461a14611dc9c27745132ded8e545c1d54c72f046997'),
|
|
38
|
+
h: BigInt(1),
|
|
39
|
+
lowS: false,
|
|
40
|
+
hash: sha2_1.sha256,
|
|
41
|
+
});
|
|
42
|
+
const brainpool384r1 = (0, weierstrass_1.weierstrass)({
|
|
43
|
+
a: BigInt('0x7bc382c63d8c150c3c72080ace05afa0c2bea28e4fb22787139165efba91f90f8aa5814a503ad4eb04a8c7dd22ce2826'),
|
|
44
|
+
b: BigInt('0x4a8c7dd22ce28268b39b55416f0447c2fb77de107dcd2a62e880ea53eeb62d57cb4390295dbc9943ab78696fa504c11'),
|
|
45
|
+
Fp: (0, modular_1.Field)(BigInt('0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a71874700133107ec53')),
|
|
46
|
+
n: BigInt('0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7cf3ab6af6b7fc3103b883202e9046565'),
|
|
47
|
+
Gx: BigInt('0x1d1c64f068cf45ffa2a63a81b7c13f6b8847a3e77ef14fe3db7fcafe0cbd10e8e826e03436d646aaef87b2e247d4af1e'),
|
|
48
|
+
Gy: BigInt('0x8abe1d7520f9c2a45cb1eb8e95cfd55262b70b29feec5864e19c054ff99129280e4646217791811142820341263c5315'),
|
|
49
|
+
h: BigInt(1),
|
|
50
|
+
lowS: false,
|
|
51
|
+
hash: sha2_1.sha384,
|
|
52
|
+
});
|
|
53
|
+
function resolveCurveFn(curve) {
|
|
54
|
+
switch (curve) {
|
|
55
|
+
case 'secp192r1':
|
|
56
|
+
return secp192r1;
|
|
57
|
+
case 'secp192k1':
|
|
58
|
+
return secp192k1;
|
|
59
|
+
case 'secp256k1':
|
|
60
|
+
return secp256k1_1.secp256k1;
|
|
61
|
+
case 'secp256r1':
|
|
62
|
+
return nist_1.secp256r1;
|
|
63
|
+
case 'secp384r1':
|
|
64
|
+
return nist_1.secp384r1;
|
|
65
|
+
case 'brainpool256r1':
|
|
66
|
+
return brainpool256r1;
|
|
67
|
+
case 'brainpool384r1':
|
|
68
|
+
return brainpool384r1;
|
|
69
|
+
default:
|
|
70
|
+
throw new Error(`Failed to resolve curve: ${curve}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.verify = exports.sign = void 0;
|
|
21
|
+
__exportStar(require("./crypto"), exports);
|
|
22
|
+
__exportStar(require("./curve"), exports);
|
|
23
|
+
__exportStar(require("./private-key"), exports);
|
|
24
|
+
__exportStar(require("./public-key"), exports);
|
|
25
|
+
var sign_1 = require("./sign");
|
|
26
|
+
Object.defineProperty(exports, "sign", { enumerable: true, get: function () { return __importDefault(sign_1).default; } });
|
|
27
|
+
var verify_1 = require("./verify");
|
|
28
|
+
Object.defineProperty(exports, "verify", { enumerable: true, get: function () { return __importDefault(verify_1).default; } });
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EcPrivateKey = void 0;
|
|
4
|
+
const public_key_1 = require("./public-key");
|
|
5
|
+
const ocmf_crypto_1 = require("@road-labs/ocmf-crypto");
|
|
6
|
+
class EcPrivateKey {
|
|
7
|
+
value;
|
|
8
|
+
curve;
|
|
9
|
+
publicKey;
|
|
10
|
+
constructor(value, curve, publicKey) {
|
|
11
|
+
this.value = value;
|
|
12
|
+
this.curve = curve;
|
|
13
|
+
this.publicKey = publicKey;
|
|
14
|
+
}
|
|
15
|
+
getValue() {
|
|
16
|
+
return this.value;
|
|
17
|
+
}
|
|
18
|
+
getCurve() {
|
|
19
|
+
return this.curve;
|
|
20
|
+
}
|
|
21
|
+
getPublicKey() {
|
|
22
|
+
return this.publicKey;
|
|
23
|
+
}
|
|
24
|
+
static fromEncoded(value, format) {
|
|
25
|
+
if (format !== 'pkcs8-der') {
|
|
26
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
27
|
+
}
|
|
28
|
+
const keyInfo = (0, ocmf_crypto_1.decodePkcs8PrivateKeyInfo)(value);
|
|
29
|
+
const namedCurve = keyInfo?.privateKey?.parameters?.namedCurve;
|
|
30
|
+
if (!namedCurve) {
|
|
31
|
+
throw new Error(`Named curve not specified`);
|
|
32
|
+
}
|
|
33
|
+
const curve = ocmf_crypto_1.oidToCurve.get(namedCurve);
|
|
34
|
+
if (!curve) {
|
|
35
|
+
throw new ocmf_crypto_1.UnsupportedCurveError(`Unknown curve: oid=${namedCurve}`);
|
|
36
|
+
}
|
|
37
|
+
let publicKey = null;
|
|
38
|
+
if (keyInfo.privateKey.publicKey) {
|
|
39
|
+
publicKey = new public_key_1.EcPublicKey(keyInfo.privateKey.publicKey, curve);
|
|
40
|
+
}
|
|
41
|
+
return new EcPrivateKey(keyInfo.privateKey.privateKey, curve, publicKey);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.EcPrivateKey = EcPrivateKey;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EcPublicKey = void 0;
|
|
4
|
+
const ocmf_crypto_1 = require("@road-labs/ocmf-crypto");
|
|
5
|
+
class EcPublicKey {
|
|
6
|
+
value;
|
|
7
|
+
curve;
|
|
8
|
+
constructor(value, curve) {
|
|
9
|
+
this.value = value;
|
|
10
|
+
this.curve = curve;
|
|
11
|
+
}
|
|
12
|
+
getValue() {
|
|
13
|
+
return this.value;
|
|
14
|
+
}
|
|
15
|
+
getCurve() {
|
|
16
|
+
return this.curve;
|
|
17
|
+
}
|
|
18
|
+
static fromEncoded(value, format) {
|
|
19
|
+
if (format !== 'spki-der') {
|
|
20
|
+
throw new ocmf_crypto_1.UnsupportedPublicKeyFormatError(`Unknown format: ${format}`);
|
|
21
|
+
}
|
|
22
|
+
const keyInfo = (0, ocmf_crypto_1.decodePkixSubjectPublicKeyInfo)(value);
|
|
23
|
+
const namedCurve = keyInfo.algorithm.parameters?.namedCurve;
|
|
24
|
+
if (!namedCurve) {
|
|
25
|
+
throw new Error(`Named curve not specified`);
|
|
26
|
+
}
|
|
27
|
+
const curve = ocmf_crypto_1.oidToCurve.get(namedCurve);
|
|
28
|
+
if (!curve) {
|
|
29
|
+
throw new ocmf_crypto_1.UnsupportedCurveError(`Unknown curve: oid=${namedCurve}`);
|
|
30
|
+
}
|
|
31
|
+
return new EcPublicKey(keyInfo.subjectPublicKey, curve);
|
|
32
|
+
}
|
|
33
|
+
encode(format) {
|
|
34
|
+
if (format !== 'spki-der') {
|
|
35
|
+
throw new ocmf_crypto_1.UnsupportedPublicKeyFormatError(`Unknown format: ${format}`);
|
|
36
|
+
}
|
|
37
|
+
const namedCurve = ocmf_crypto_1.curveToOid.get(this.curve);
|
|
38
|
+
if (!namedCurve) {
|
|
39
|
+
throw new Error(`Failed to map curve to OID: ${this.curve}`);
|
|
40
|
+
}
|
|
41
|
+
return (0, ocmf_crypto_1.encodePkixSubjectPublicKeyInfo)({
|
|
42
|
+
algorithm: {
|
|
43
|
+
algorithm: ocmf_crypto_1.oidEllipticCurveKey,
|
|
44
|
+
parameters: {
|
|
45
|
+
namedCurve,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
subjectPublicKey: this.value,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.EcPublicKey = EcPublicKey;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = sign;
|
|
4
|
+
const sha2_1 = require("@noble/hashes/sha2");
|
|
5
|
+
const curve_1 = require("./curve");
|
|
6
|
+
const ocmf_crypto_1 = require("@road-labs/ocmf-crypto");
|
|
7
|
+
function sign(data, privateKey, hash, format) {
|
|
8
|
+
if (hash !== 'SHA-256') {
|
|
9
|
+
throw new ocmf_crypto_1.UnsupportedHashError(`Invalid hash: ${hash}`);
|
|
10
|
+
}
|
|
11
|
+
if (format !== 'sigvalue-der') {
|
|
12
|
+
throw new ocmf_crypto_1.UnsupportedSignatureFormatError(`Invalid signature format: ${format}`);
|
|
13
|
+
}
|
|
14
|
+
return (0, curve_1.resolveCurveFn)(privateKey.getCurve())
|
|
15
|
+
.sign((0, sha2_1.sha256)(data), privateKey.getValue())
|
|
16
|
+
.toDERRawBytes();
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = verify;
|
|
4
|
+
const sha2_1 = require("@noble/hashes/sha2");
|
|
5
|
+
const curve_1 = require("./curve");
|
|
6
|
+
const ocmf_crypto_1 = require("@road-labs/ocmf-crypto");
|
|
7
|
+
function verify(signature, data, key, hash, format) {
|
|
8
|
+
if (hash !== 'SHA-256') {
|
|
9
|
+
throw new ocmf_crypto_1.UnsupportedHashError(`Invalid hash: ${hash}`);
|
|
10
|
+
}
|
|
11
|
+
if (format !== 'sigvalue-der') {
|
|
12
|
+
throw new ocmf_crypto_1.UnsupportedSignatureFormatError(`Invalid signature format: ${format}`);
|
|
13
|
+
}
|
|
14
|
+
return (0, curve_1.resolveCurveFn)(key.getCurve()).verify(signature, (0, sha2_1.sha256)(data), key.getValue(), { format: 'der' });
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EcPrivateKey } from './private-key';
|
|
2
|
+
import { EcPublicKey } from './public-key';
|
|
3
|
+
import sign from './sign';
|
|
4
|
+
import verify from './verify';
|
|
5
|
+
export class Crypto {
|
|
6
|
+
async decodeEcPrivateKey(value, format) {
|
|
7
|
+
return EcPrivateKey.fromEncoded(value, format);
|
|
8
|
+
}
|
|
9
|
+
async decodeEcPublicKey(value, format) {
|
|
10
|
+
return EcPublicKey.fromEncoded(value, format);
|
|
11
|
+
}
|
|
12
|
+
async sign(data, privateKey, hash, format) {
|
|
13
|
+
return sign(data, privateKey, hash, format);
|
|
14
|
+
}
|
|
15
|
+
async verify(signature, data, publicKey, hash, format) {
|
|
16
|
+
return verify(signature, data, publicKey, hash, format);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
2
|
+
import { secp256r1, secp384r1 } from '@noble/curves/nist';
|
|
3
|
+
import { Field } from '@noble/curves/abstract/modular';
|
|
4
|
+
import { sha256, sha384 } from '@noble/hashes/sha2';
|
|
5
|
+
import { weierstrass } from '@noble/curves/abstract/weierstrass';
|
|
6
|
+
const secp192r1 = weierstrass({
|
|
7
|
+
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffc'),
|
|
8
|
+
b: BigInt('0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1'),
|
|
9
|
+
Fp: Field(BigInt('0xfffffffffffffffffffffffffffffffeffffffffffffffff')),
|
|
10
|
+
n: BigInt('0xffffffffffffffffffffffff99def836146bc9b1b4d22831'),
|
|
11
|
+
Gx: BigInt('0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012'),
|
|
12
|
+
Gy: BigInt('0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811'),
|
|
13
|
+
h: BigInt(1),
|
|
14
|
+
lowS: false,
|
|
15
|
+
hash: sha256,
|
|
16
|
+
});
|
|
17
|
+
const secp192k1 = weierstrass({
|
|
18
|
+
a: BigInt(0),
|
|
19
|
+
b: BigInt(3),
|
|
20
|
+
Fp: Field(BigInt('0xfffffffffffffffffffffffffffffffffffffffeffffee37')),
|
|
21
|
+
n: BigInt('0xfffffffffffffffffffffffe26f2fc170f69466a74defd8d'),
|
|
22
|
+
Gx: BigInt('0xdb4ff10ec057e9ae26b07d0280b7f4341da5d1b1eae06c7d'),
|
|
23
|
+
Gy: BigInt('0x9b2f2f6d9c5628a7844163d015be86344082aa88d95e2f9d'),
|
|
24
|
+
h: BigInt(1),
|
|
25
|
+
lowS: false,
|
|
26
|
+
hash: sha256,
|
|
27
|
+
});
|
|
28
|
+
const brainpool256r1 = weierstrass({
|
|
29
|
+
a: BigInt('0x7d5a0975fc2c3057eef67530417affe7fb8055c126dc5c6ce94a4b44f330b5d9'),
|
|
30
|
+
b: BigInt('0x26dc5c6ce94a4b44f330b5d9bbd77cbf958416295cf7e1ce6bccdc18ff8c07b6'),
|
|
31
|
+
Fp: Field(BigInt('0xa9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5377')),
|
|
32
|
+
n: BigInt('0xa9fb57dba1eea9bc3e660a909d838d718c397aa3b561a6f7901e0e82974856a7'),
|
|
33
|
+
Gx: BigInt('0x8bd2aeb9cb7e57cb2c4b482ffc81b7afb9de27e1e3bd23c23a4453bd9ace3262'),
|
|
34
|
+
Gy: BigInt('0x547ef835c3dac4fd97f8461a14611dc9c27745132ded8e545c1d54c72f046997'),
|
|
35
|
+
h: BigInt(1),
|
|
36
|
+
lowS: false,
|
|
37
|
+
hash: sha256,
|
|
38
|
+
});
|
|
39
|
+
const brainpool384r1 = weierstrass({
|
|
40
|
+
a: BigInt('0x7bc382c63d8c150c3c72080ace05afa0c2bea28e4fb22787139165efba91f90f8aa5814a503ad4eb04a8c7dd22ce2826'),
|
|
41
|
+
b: BigInt('0x4a8c7dd22ce28268b39b55416f0447c2fb77de107dcd2a62e880ea53eeb62d57cb4390295dbc9943ab78696fa504c11'),
|
|
42
|
+
Fp: Field(BigInt('0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a71874700133107ec53')),
|
|
43
|
+
n: BigInt('0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7cf3ab6af6b7fc3103b883202e9046565'),
|
|
44
|
+
Gx: BigInt('0x1d1c64f068cf45ffa2a63a81b7c13f6b8847a3e77ef14fe3db7fcafe0cbd10e8e826e03436d646aaef87b2e247d4af1e'),
|
|
45
|
+
Gy: BigInt('0x8abe1d7520f9c2a45cb1eb8e95cfd55262b70b29feec5864e19c054ff99129280e4646217791811142820341263c5315'),
|
|
46
|
+
h: BigInt(1),
|
|
47
|
+
lowS: false,
|
|
48
|
+
hash: sha384,
|
|
49
|
+
});
|
|
50
|
+
export function resolveCurveFn(curve) {
|
|
51
|
+
switch (curve) {
|
|
52
|
+
case 'secp192r1':
|
|
53
|
+
return secp192r1;
|
|
54
|
+
case 'secp192k1':
|
|
55
|
+
return secp192k1;
|
|
56
|
+
case 'secp256k1':
|
|
57
|
+
return secp256k1;
|
|
58
|
+
case 'secp256r1':
|
|
59
|
+
return secp256r1;
|
|
60
|
+
case 'secp384r1':
|
|
61
|
+
return secp384r1;
|
|
62
|
+
case 'brainpool256r1':
|
|
63
|
+
return brainpool256r1;
|
|
64
|
+
case 'brainpool384r1':
|
|
65
|
+
return brainpool384r1;
|
|
66
|
+
default:
|
|
67
|
+
throw new Error(`Failed to resolve curve: ${curve}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { EcPublicKey } from './public-key';
|
|
2
|
+
import { decodePkcs8PrivateKeyInfo, oidToCurve, UnsupportedCurveError, } from '@road-labs/ocmf-crypto';
|
|
3
|
+
export class EcPrivateKey {
|
|
4
|
+
value;
|
|
5
|
+
curve;
|
|
6
|
+
publicKey;
|
|
7
|
+
constructor(value, curve, publicKey) {
|
|
8
|
+
this.value = value;
|
|
9
|
+
this.curve = curve;
|
|
10
|
+
this.publicKey = publicKey;
|
|
11
|
+
}
|
|
12
|
+
getValue() {
|
|
13
|
+
return this.value;
|
|
14
|
+
}
|
|
15
|
+
getCurve() {
|
|
16
|
+
return this.curve;
|
|
17
|
+
}
|
|
18
|
+
getPublicKey() {
|
|
19
|
+
return this.publicKey;
|
|
20
|
+
}
|
|
21
|
+
static fromEncoded(value, format) {
|
|
22
|
+
if (format !== 'pkcs8-der') {
|
|
23
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
24
|
+
}
|
|
25
|
+
const keyInfo = decodePkcs8PrivateKeyInfo(value);
|
|
26
|
+
const namedCurve = keyInfo?.privateKey?.parameters?.namedCurve;
|
|
27
|
+
if (!namedCurve) {
|
|
28
|
+
throw new Error(`Named curve not specified`);
|
|
29
|
+
}
|
|
30
|
+
const curve = oidToCurve.get(namedCurve);
|
|
31
|
+
if (!curve) {
|
|
32
|
+
throw new UnsupportedCurveError(`Unknown curve: oid=${namedCurve}`);
|
|
33
|
+
}
|
|
34
|
+
let publicKey = null;
|
|
35
|
+
if (keyInfo.privateKey.publicKey) {
|
|
36
|
+
publicKey = new EcPublicKey(keyInfo.privateKey.publicKey, curve);
|
|
37
|
+
}
|
|
38
|
+
return new EcPrivateKey(keyInfo.privateKey.privateKey, curve, publicKey);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { curveToOid, decodePkixSubjectPublicKeyInfo, encodePkixSubjectPublicKeyInfo, oidEllipticCurveKey, oidToCurve, UnsupportedCurveError, UnsupportedPublicKeyFormatError, } from '@road-labs/ocmf-crypto';
|
|
2
|
+
export class EcPublicKey {
|
|
3
|
+
value;
|
|
4
|
+
curve;
|
|
5
|
+
constructor(value, curve) {
|
|
6
|
+
this.value = value;
|
|
7
|
+
this.curve = curve;
|
|
8
|
+
}
|
|
9
|
+
getValue() {
|
|
10
|
+
return this.value;
|
|
11
|
+
}
|
|
12
|
+
getCurve() {
|
|
13
|
+
return this.curve;
|
|
14
|
+
}
|
|
15
|
+
static fromEncoded(value, format) {
|
|
16
|
+
if (format !== 'spki-der') {
|
|
17
|
+
throw new UnsupportedPublicKeyFormatError(`Unknown format: ${format}`);
|
|
18
|
+
}
|
|
19
|
+
const keyInfo = decodePkixSubjectPublicKeyInfo(value);
|
|
20
|
+
const namedCurve = keyInfo.algorithm.parameters?.namedCurve;
|
|
21
|
+
if (!namedCurve) {
|
|
22
|
+
throw new Error(`Named curve not specified`);
|
|
23
|
+
}
|
|
24
|
+
const curve = oidToCurve.get(namedCurve);
|
|
25
|
+
if (!curve) {
|
|
26
|
+
throw new UnsupportedCurveError(`Unknown curve: oid=${namedCurve}`);
|
|
27
|
+
}
|
|
28
|
+
return new EcPublicKey(keyInfo.subjectPublicKey, curve);
|
|
29
|
+
}
|
|
30
|
+
encode(format) {
|
|
31
|
+
if (format !== 'spki-der') {
|
|
32
|
+
throw new UnsupportedPublicKeyFormatError(`Unknown format: ${format}`);
|
|
33
|
+
}
|
|
34
|
+
const namedCurve = curveToOid.get(this.curve);
|
|
35
|
+
if (!namedCurve) {
|
|
36
|
+
throw new Error(`Failed to map curve to OID: ${this.curve}`);
|
|
37
|
+
}
|
|
38
|
+
return encodePkixSubjectPublicKeyInfo({
|
|
39
|
+
algorithm: {
|
|
40
|
+
algorithm: oidEllipticCurveKey,
|
|
41
|
+
parameters: {
|
|
42
|
+
namedCurve,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
subjectPublicKey: this.value,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { sha256 } from '@noble/hashes/sha2';
|
|
2
|
+
import { resolveCurveFn } from './curve';
|
|
3
|
+
import { UnsupportedHashError, UnsupportedSignatureFormatError, } from '@road-labs/ocmf-crypto';
|
|
4
|
+
export default function sign(data, privateKey, hash, format) {
|
|
5
|
+
if (hash !== 'SHA-256') {
|
|
6
|
+
throw new UnsupportedHashError(`Invalid hash: ${hash}`);
|
|
7
|
+
}
|
|
8
|
+
if (format !== 'sigvalue-der') {
|
|
9
|
+
throw new UnsupportedSignatureFormatError(`Invalid signature format: ${format}`);
|
|
10
|
+
}
|
|
11
|
+
return resolveCurveFn(privateKey.getCurve())
|
|
12
|
+
.sign(sha256(data), privateKey.getValue())
|
|
13
|
+
.toDERRawBytes();
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { sha256 } from '@noble/hashes/sha2';
|
|
2
|
+
import { resolveCurveFn } from './curve';
|
|
3
|
+
import { UnsupportedHashError, UnsupportedSignatureFormatError, } from '@road-labs/ocmf-crypto';
|
|
4
|
+
export default function verify(signature, data, key, hash, format) {
|
|
5
|
+
if (hash !== 'SHA-256') {
|
|
6
|
+
throw new UnsupportedHashError(`Invalid hash: ${hash}`);
|
|
7
|
+
}
|
|
8
|
+
if (format !== 'sigvalue-der') {
|
|
9
|
+
throw new UnsupportedSignatureFormatError(`Invalid signature format: ${format}`);
|
|
10
|
+
}
|
|
11
|
+
return resolveCurveFn(key.getCurve()).verify(signature, sha256(data), key.getValue(), { format: 'der' });
|
|
12
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CryptoAdapter, Hash, PrivateKeyFormat, PublicKeyFormat, SignatureFormat } from '@road-labs/ocmf-crypto';
|
|
2
|
+
import { EcPrivateKey } from './private-key';
|
|
3
|
+
import { EcPublicKey } from './public-key';
|
|
4
|
+
export declare class Crypto implements CryptoAdapter {
|
|
5
|
+
decodeEcPrivateKey(value: Uint8Array, format: PrivateKeyFormat): Promise<EcPrivateKey>;
|
|
6
|
+
decodeEcPublicKey(value: Uint8Array, format: PublicKeyFormat): Promise<EcPublicKey>;
|
|
7
|
+
sign(data: Uint8Array, privateKey: EcPrivateKey, hash: Hash, format: SignatureFormat): Promise<Uint8Array>;
|
|
8
|
+
verify(signature: Uint8Array, data: Uint8Array, publicKey: EcPublicKey, hash: Hash, format: SignatureFormat): Promise<boolean>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { EcPublicKey } from './public-key';
|
|
2
|
+
import { Curve, PrivateKeyFormat } from '@road-labs/ocmf-crypto';
|
|
3
|
+
export declare class EcPrivateKey {
|
|
4
|
+
private readonly value;
|
|
5
|
+
private readonly curve;
|
|
6
|
+
private readonly publicKey;
|
|
7
|
+
private constructor();
|
|
8
|
+
getValue(): Uint8Array;
|
|
9
|
+
getCurve(): Curve;
|
|
10
|
+
getPublicKey(): EcPublicKey | null;
|
|
11
|
+
/**
|
|
12
|
+
* @param value - Encoded private key value
|
|
13
|
+
* @param format - Encoding format
|
|
14
|
+
*/
|
|
15
|
+
static fromEncoded(value: Uint8Array, format: PrivateKeyFormat): EcPrivateKey;
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Curve, PublicKeyFormat } from '@road-labs/ocmf-crypto';
|
|
2
|
+
export declare class EcPublicKey {
|
|
3
|
+
private readonly value;
|
|
4
|
+
private readonly curve;
|
|
5
|
+
constructor(value: Uint8Array, curve: Curve);
|
|
6
|
+
getValue(): Uint8Array;
|
|
7
|
+
getCurve(): Curve;
|
|
8
|
+
static fromEncoded(value: Uint8Array, format: PublicKeyFormat): EcPublicKey;
|
|
9
|
+
encode(format: PublicKeyFormat): Uint8Array;
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { EcPrivateKey } from './private-key';
|
|
2
|
+
import { Hash, SignatureFormat } from '@road-labs/ocmf-crypto';
|
|
3
|
+
/**
|
|
4
|
+
* @param data - Data to be signed
|
|
5
|
+
* @param privateKey - Private key to use for signing
|
|
6
|
+
* @param hash - Hash to apply
|
|
7
|
+
* @param format - Signature format
|
|
8
|
+
* @return X.509 ECDSASigValue ASN.1 type DER encoded
|
|
9
|
+
*/
|
|
10
|
+
export default function sign(data: Uint8Array, privateKey: EcPrivateKey, hash: Hash, format: SignatureFormat): Uint8Array;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { EcPublicKey } from './public-key';
|
|
2
|
+
import { Hash, SignatureFormat } from '@road-labs/ocmf-crypto';
|
|
3
|
+
/**
|
|
4
|
+
* @param signature - X.509 ECDSASigValue ASN.1 type DER encoded
|
|
5
|
+
* @param data - Raw value
|
|
6
|
+
* @param key - The public key to verify against
|
|
7
|
+
* @param hash - Hash to apply
|
|
8
|
+
* @param format - Signature format
|
|
9
|
+
*/
|
|
10
|
+
export default function verify(signature: Uint8Array, data: Uint8Array, key: EcPublicKey, hash: Hash, format: SignatureFormat): boolean;
|
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@road-labs/ocmf-crypto-noble",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"main": "build/cjs/index.js",
|
|
5
|
+
"module": "build/es2022/index.js",
|
|
6
|
+
"types": "build/types/index.d.ts",
|
|
7
|
+
"keywords": [],
|
|
8
|
+
"author": "",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"description": "",
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@jest/globals": "^30.0.0",
|
|
13
|
+
"@types/node": "^22.15.30",
|
|
14
|
+
"jest": "^30.0.0",
|
|
15
|
+
"rimraf": "^6.0.1",
|
|
16
|
+
"ts-jest": "^29.4.0",
|
|
17
|
+
"ts-node": "^10.9.2",
|
|
18
|
+
"typescript": "^5.8.3",
|
|
19
|
+
"test-commons": "0.0.1"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@noble/curves": "^1.9.2",
|
|
23
|
+
"@noble/hashes": "^1.8.0",
|
|
24
|
+
"@road-labs/ocmf-crypto": "0.0.1"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"clear": "rimraf build",
|
|
28
|
+
"build": "pnpm run build:module && pnpm run build:types",
|
|
29
|
+
"build:module": "pnpm run build:cjs && pnpm run build:es2022",
|
|
30
|
+
"build:cjs": "tsc -p tsconfig.json --removeComments --module commonjs --outDir build/cjs",
|
|
31
|
+
"build:es2022": "tsc -p tsconfig.json --removeComments --module es2022 --outDir build/es2022",
|
|
32
|
+
"prebuild:types": "rimraf build/types",
|
|
33
|
+
"build:types": "tsc -p tsconfig.json --outDir build/types --declaration --emitDeclarationOnly",
|
|
34
|
+
"rebuild": "pnpm run clear && pnpm run build",
|
|
35
|
+
"test": "jest"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/crypto.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CryptoAdapter,
|
|
3
|
+
Hash,
|
|
4
|
+
PrivateKeyFormat,
|
|
5
|
+
PublicKeyFormat,
|
|
6
|
+
SignatureFormat,
|
|
7
|
+
} from '@road-labs/ocmf-crypto';
|
|
8
|
+
import { EcPrivateKey } from './private-key';
|
|
9
|
+
import { EcPublicKey } from './public-key';
|
|
10
|
+
import sign from './sign';
|
|
11
|
+
import verify from './verify';
|
|
12
|
+
|
|
13
|
+
export class Crypto implements CryptoAdapter {
|
|
14
|
+
async decodeEcPrivateKey(
|
|
15
|
+
value: Uint8Array,
|
|
16
|
+
format: PrivateKeyFormat
|
|
17
|
+
): Promise<EcPrivateKey> {
|
|
18
|
+
return EcPrivateKey.fromEncoded(value, format);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async decodeEcPublicKey(
|
|
22
|
+
value: Uint8Array,
|
|
23
|
+
format: PublicKeyFormat
|
|
24
|
+
): Promise<EcPublicKey> {
|
|
25
|
+
return EcPublicKey.fromEncoded(value, format);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async sign(
|
|
29
|
+
data: Uint8Array,
|
|
30
|
+
privateKey: EcPrivateKey,
|
|
31
|
+
hash: Hash,
|
|
32
|
+
format: SignatureFormat
|
|
33
|
+
): Promise<Uint8Array> {
|
|
34
|
+
return sign(data, privateKey, hash, format);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async verify(
|
|
38
|
+
signature: Uint8Array,
|
|
39
|
+
data: Uint8Array,
|
|
40
|
+
publicKey: EcPublicKey,
|
|
41
|
+
hash: Hash,
|
|
42
|
+
format: SignatureFormat
|
|
43
|
+
): Promise<boolean> {
|
|
44
|
+
return verify(signature, data, publicKey, hash, format);
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/curve.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
2
|
+
import { secp256r1, secp384r1 } from '@noble/curves/nist';
|
|
3
|
+
import { Field } from '@noble/curves/abstract/modular';
|
|
4
|
+
import { sha256, sha384 } from '@noble/hashes/sha2';
|
|
5
|
+
import { Curve } from '@road-labs/ocmf-crypto';
|
|
6
|
+
import { CurveFn, weierstrass } from '@noble/curves/abstract/weierstrass';
|
|
7
|
+
|
|
8
|
+
const secp192r1 = weierstrass({
|
|
9
|
+
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffc'),
|
|
10
|
+
b: BigInt('0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1'),
|
|
11
|
+
Fp: Field(BigInt('0xfffffffffffffffffffffffffffffffeffffffffffffffff')),
|
|
12
|
+
n: BigInt('0xffffffffffffffffffffffff99def836146bc9b1b4d22831'),
|
|
13
|
+
Gx: BigInt('0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012'),
|
|
14
|
+
Gy: BigInt('0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811'),
|
|
15
|
+
h: BigInt(1),
|
|
16
|
+
lowS: false,
|
|
17
|
+
hash: sha256,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const secp192k1 = weierstrass({
|
|
21
|
+
a: BigInt(0),
|
|
22
|
+
b: BigInt(3),
|
|
23
|
+
Fp: Field(BigInt('0xfffffffffffffffffffffffffffffffffffffffeffffee37')),
|
|
24
|
+
n: BigInt('0xfffffffffffffffffffffffe26f2fc170f69466a74defd8d'),
|
|
25
|
+
Gx: BigInt('0xdb4ff10ec057e9ae26b07d0280b7f4341da5d1b1eae06c7d'),
|
|
26
|
+
Gy: BigInt('0x9b2f2f6d9c5628a7844163d015be86344082aa88d95e2f9d'),
|
|
27
|
+
h: BigInt(1),
|
|
28
|
+
lowS: false,
|
|
29
|
+
hash: sha256,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const brainpool256r1 = weierstrass({
|
|
33
|
+
a: BigInt(
|
|
34
|
+
'0x7d5a0975fc2c3057eef67530417affe7fb8055c126dc5c6ce94a4b44f330b5d9'
|
|
35
|
+
),
|
|
36
|
+
b: BigInt(
|
|
37
|
+
'0x26dc5c6ce94a4b44f330b5d9bbd77cbf958416295cf7e1ce6bccdc18ff8c07b6'
|
|
38
|
+
),
|
|
39
|
+
Fp: Field(
|
|
40
|
+
BigInt('0xa9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5377')
|
|
41
|
+
),
|
|
42
|
+
n: BigInt(
|
|
43
|
+
'0xa9fb57dba1eea9bc3e660a909d838d718c397aa3b561a6f7901e0e82974856a7'
|
|
44
|
+
),
|
|
45
|
+
Gx: BigInt(
|
|
46
|
+
'0x8bd2aeb9cb7e57cb2c4b482ffc81b7afb9de27e1e3bd23c23a4453bd9ace3262'
|
|
47
|
+
),
|
|
48
|
+
Gy: BigInt(
|
|
49
|
+
'0x547ef835c3dac4fd97f8461a14611dc9c27745132ded8e545c1d54c72f046997'
|
|
50
|
+
),
|
|
51
|
+
h: BigInt(1),
|
|
52
|
+
lowS: false,
|
|
53
|
+
hash: sha256,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const brainpool384r1 = weierstrass({
|
|
57
|
+
a: BigInt(
|
|
58
|
+
'0x7bc382c63d8c150c3c72080ace05afa0c2bea28e4fb22787139165efba91f90f8aa5814a503ad4eb04a8c7dd22ce2826'
|
|
59
|
+
),
|
|
60
|
+
b: BigInt(
|
|
61
|
+
'0x4a8c7dd22ce28268b39b55416f0447c2fb77de107dcd2a62e880ea53eeb62d57cb4390295dbc9943ab78696fa504c11'
|
|
62
|
+
),
|
|
63
|
+
Fp: Field(
|
|
64
|
+
BigInt(
|
|
65
|
+
'0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a71874700133107ec53'
|
|
66
|
+
)
|
|
67
|
+
),
|
|
68
|
+
n: BigInt(
|
|
69
|
+
'0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7cf3ab6af6b7fc3103b883202e9046565'
|
|
70
|
+
),
|
|
71
|
+
Gx: BigInt(
|
|
72
|
+
'0x1d1c64f068cf45ffa2a63a81b7c13f6b8847a3e77ef14fe3db7fcafe0cbd10e8e826e03436d646aaef87b2e247d4af1e'
|
|
73
|
+
),
|
|
74
|
+
Gy: BigInt(
|
|
75
|
+
'0x8abe1d7520f9c2a45cb1eb8e95cfd55262b70b29feec5864e19c054ff99129280e4646217791811142820341263c5315'
|
|
76
|
+
),
|
|
77
|
+
h: BigInt(1),
|
|
78
|
+
lowS: false,
|
|
79
|
+
hash: sha384,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export function resolveCurveFn(curve: Curve): CurveFn {
|
|
83
|
+
switch (curve) {
|
|
84
|
+
case 'secp192r1':
|
|
85
|
+
return secp192r1;
|
|
86
|
+
case 'secp192k1':
|
|
87
|
+
return secp192k1;
|
|
88
|
+
case 'secp256k1':
|
|
89
|
+
return secp256k1;
|
|
90
|
+
case 'secp256r1':
|
|
91
|
+
return secp256r1;
|
|
92
|
+
case 'secp384r1':
|
|
93
|
+
return secp384r1;
|
|
94
|
+
case 'brainpool256r1':
|
|
95
|
+
return brainpool256r1;
|
|
96
|
+
case 'brainpool384r1':
|
|
97
|
+
return brainpool384r1;
|
|
98
|
+
default:
|
|
99
|
+
throw new Error(`Failed to resolve curve: ${curve}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { EcPublicKey } from './public-key';
|
|
2
|
+
import {
|
|
3
|
+
Curve,
|
|
4
|
+
decodePkcs8PrivateKeyInfo,
|
|
5
|
+
oidToCurve,
|
|
6
|
+
PrivateKeyFormat,
|
|
7
|
+
UnsupportedCurveError,
|
|
8
|
+
} from '@road-labs/ocmf-crypto';
|
|
9
|
+
|
|
10
|
+
export class EcPrivateKey {
|
|
11
|
+
private constructor(
|
|
12
|
+
private readonly value: Uint8Array,
|
|
13
|
+
private readonly curve: Curve,
|
|
14
|
+
private readonly publicKey: EcPublicKey | null
|
|
15
|
+
) {}
|
|
16
|
+
|
|
17
|
+
public getValue(): Uint8Array {
|
|
18
|
+
return this.value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public getCurve(): Curve {
|
|
22
|
+
return this.curve;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public getPublicKey(): EcPublicKey | null {
|
|
26
|
+
return this.publicKey;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param value - Encoded private key value
|
|
31
|
+
* @param format - Encoding format
|
|
32
|
+
*/
|
|
33
|
+
static fromEncoded(
|
|
34
|
+
value: Uint8Array,
|
|
35
|
+
format: PrivateKeyFormat
|
|
36
|
+
): EcPrivateKey {
|
|
37
|
+
if (format !== 'pkcs8-der') {
|
|
38
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const keyInfo = decodePkcs8PrivateKeyInfo(value);
|
|
42
|
+
|
|
43
|
+
const namedCurve = keyInfo?.privateKey?.parameters?.namedCurve;
|
|
44
|
+
if (!namedCurve) {
|
|
45
|
+
throw new Error(`Named curve not specified`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const curve = oidToCurve.get(namedCurve);
|
|
49
|
+
if (!curve) {
|
|
50
|
+
throw new UnsupportedCurveError(`Unknown curve: oid=${namedCurve}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let publicKey: EcPublicKey | null = null;
|
|
54
|
+
if (keyInfo.privateKey.publicKey) {
|
|
55
|
+
publicKey = new EcPublicKey(keyInfo.privateKey.publicKey, curve);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return new EcPrivateKey(keyInfo.privateKey.privateKey, curve, publicKey);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Curve,
|
|
3
|
+
curveToOid,
|
|
4
|
+
decodePkixSubjectPublicKeyInfo,
|
|
5
|
+
encodePkixSubjectPublicKeyInfo,
|
|
6
|
+
oidEllipticCurveKey,
|
|
7
|
+
oidToCurve,
|
|
8
|
+
PublicKeyFormat,
|
|
9
|
+
UnsupportedCurveError,
|
|
10
|
+
UnsupportedPublicKeyFormatError,
|
|
11
|
+
} from '@road-labs/ocmf-crypto';
|
|
12
|
+
|
|
13
|
+
export class EcPublicKey {
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly value: Uint8Array,
|
|
16
|
+
private readonly curve: Curve
|
|
17
|
+
) {}
|
|
18
|
+
|
|
19
|
+
public getValue(): Uint8Array {
|
|
20
|
+
return this.value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public getCurve(): Curve {
|
|
24
|
+
return this.curve;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static fromEncoded(value: Uint8Array, format: PublicKeyFormat): EcPublicKey {
|
|
28
|
+
if (format !== 'spki-der') {
|
|
29
|
+
throw new UnsupportedPublicKeyFormatError(`Unknown format: ${format}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const keyInfo = decodePkixSubjectPublicKeyInfo(value);
|
|
33
|
+
|
|
34
|
+
const namedCurve = keyInfo.algorithm.parameters?.namedCurve;
|
|
35
|
+
if (!namedCurve) {
|
|
36
|
+
throw new Error(`Named curve not specified`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const curve = oidToCurve.get(namedCurve);
|
|
40
|
+
if (!curve) {
|
|
41
|
+
throw new UnsupportedCurveError(`Unknown curve: oid=${namedCurve}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return new EcPublicKey(keyInfo.subjectPublicKey, curve);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public encode(format: PublicKeyFormat): Uint8Array {
|
|
48
|
+
if (format !== 'spki-der') {
|
|
49
|
+
throw new UnsupportedPublicKeyFormatError(`Unknown format: ${format}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const namedCurve = curveToOid.get(this.curve);
|
|
53
|
+
if (!namedCurve) {
|
|
54
|
+
throw new Error(`Failed to map curve to OID: ${this.curve}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return encodePkixSubjectPublicKeyInfo({
|
|
58
|
+
algorithm: {
|
|
59
|
+
algorithm: oidEllipticCurveKey,
|
|
60
|
+
parameters: {
|
|
61
|
+
namedCurve,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
subjectPublicKey: this.value,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/sign.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { EcPrivateKey } from './private-key';
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha2';
|
|
3
|
+
import { resolveCurveFn } from './curve';
|
|
4
|
+
import {
|
|
5
|
+
Hash,
|
|
6
|
+
SignatureFormat,
|
|
7
|
+
UnsupportedHashError,
|
|
8
|
+
UnsupportedSignatureFormatError,
|
|
9
|
+
} from '@road-labs/ocmf-crypto';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param data - Data to be signed
|
|
13
|
+
* @param privateKey - Private key to use for signing
|
|
14
|
+
* @param hash - Hash to apply
|
|
15
|
+
* @param format - Signature format
|
|
16
|
+
* @return X.509 ECDSASigValue ASN.1 type DER encoded
|
|
17
|
+
*/
|
|
18
|
+
export default function sign(
|
|
19
|
+
data: Uint8Array,
|
|
20
|
+
privateKey: EcPrivateKey,
|
|
21
|
+
hash: Hash,
|
|
22
|
+
format: SignatureFormat
|
|
23
|
+
): Uint8Array {
|
|
24
|
+
if (hash !== 'SHA-256') {
|
|
25
|
+
throw new UnsupportedHashError(`Invalid hash: ${hash}`);
|
|
26
|
+
}
|
|
27
|
+
if (format !== 'sigvalue-der') {
|
|
28
|
+
throw new UnsupportedSignatureFormatError(
|
|
29
|
+
`Invalid signature format: ${format}`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return resolveCurveFn(privateKey.getCurve())
|
|
34
|
+
.sign(sha256(data), privateKey.getValue())
|
|
35
|
+
.toDERRawBytes();
|
|
36
|
+
}
|
package/src/verify.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { EcPublicKey } from './public-key';
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha2';
|
|
3
|
+
import { resolveCurveFn } from './curve';
|
|
4
|
+
import {
|
|
5
|
+
Hash,
|
|
6
|
+
SignatureFormat,
|
|
7
|
+
UnsupportedHashError,
|
|
8
|
+
UnsupportedSignatureFormatError,
|
|
9
|
+
} from '@road-labs/ocmf-crypto';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param signature - X.509 ECDSASigValue ASN.1 type DER encoded
|
|
13
|
+
* @param data - Raw value
|
|
14
|
+
* @param key - The public key to verify against
|
|
15
|
+
* @param hash - Hash to apply
|
|
16
|
+
* @param format - Signature format
|
|
17
|
+
*/
|
|
18
|
+
export default function verify(
|
|
19
|
+
signature: Uint8Array,
|
|
20
|
+
data: Uint8Array,
|
|
21
|
+
key: EcPublicKey,
|
|
22
|
+
hash: Hash,
|
|
23
|
+
format: SignatureFormat
|
|
24
|
+
): boolean {
|
|
25
|
+
if (hash !== 'SHA-256') {
|
|
26
|
+
throw new UnsupportedHashError(`Invalid hash: ${hash}`);
|
|
27
|
+
}
|
|
28
|
+
if (format !== 'sigvalue-der') {
|
|
29
|
+
throw new UnsupportedSignatureFormatError(
|
|
30
|
+
`Invalid signature format: ${format}`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return resolveCurveFn(key.getCurve()).verify(
|
|
35
|
+
signature,
|
|
36
|
+
sha256(data),
|
|
37
|
+
key.getValue(),
|
|
38
|
+
{ format: 'der' }
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
import { Curve } from '@road-labs/ocmf-crypto';
|
|
3
|
+
import { buildPrivateKeyTestCases } from 'test-commons';
|
|
4
|
+
import { EcPrivateKey } from '../src';
|
|
5
|
+
|
|
6
|
+
const curves: Curve[] = [
|
|
7
|
+
'brainpool256r1',
|
|
8
|
+
'brainpool384r1',
|
|
9
|
+
'secp192r1',
|
|
10
|
+
'secp192k1',
|
|
11
|
+
'secp256k1',
|
|
12
|
+
'secp256r1',
|
|
13
|
+
'secp384r1',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
describe('EcPrivateKey', () => {
|
|
17
|
+
describe('fromEncoded', () => {
|
|
18
|
+
it.each(buildPrivateKeyTestCases(curves))(
|
|
19
|
+
'supports $name',
|
|
20
|
+
({ curve, pkcs8 }) => {
|
|
21
|
+
const privateKey = EcPrivateKey.fromEncoded(pkcs8, 'pkcs8-der');
|
|
22
|
+
expect(privateKey.getCurve()).toEqual(curve);
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('getPublicKey', () => {
|
|
28
|
+
it.each(buildPrivateKeyTestCases(curves))(
|
|
29
|
+
'can derive a public key for $name',
|
|
30
|
+
({ curve, pkcs8, spki }) => {
|
|
31
|
+
const publicKey = EcPrivateKey.fromEncoded(
|
|
32
|
+
pkcs8,
|
|
33
|
+
'pkcs8-der'
|
|
34
|
+
).getPublicKey();
|
|
35
|
+
expect(publicKey?.getCurve()).toEqual(curve);
|
|
36
|
+
expect(publicKey?.encode('spki-der')).toEqual(spki);
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
import { Curve } from '@road-labs/ocmf-crypto';
|
|
3
|
+
import { buildPublicKeyTestCases, isValidPublicKey } from 'test-commons';
|
|
4
|
+
import { EcPublicKey } from '../src';
|
|
5
|
+
|
|
6
|
+
const curves: Curve[] = [
|
|
7
|
+
'brainpool256r1',
|
|
8
|
+
'brainpool384r1',
|
|
9
|
+
'secp192r1',
|
|
10
|
+
'secp192k1',
|
|
11
|
+
'secp256k1',
|
|
12
|
+
'secp256r1',
|
|
13
|
+
'secp384r1',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
describe('EcPublicKey', () => {
|
|
17
|
+
describe('fromEncoded', () => {
|
|
18
|
+
it.each(buildPublicKeyTestCases(curves))(
|
|
19
|
+
'supports $name',
|
|
20
|
+
({ curve, spki }) => {
|
|
21
|
+
const publicKey = EcPublicKey.fromEncoded(spki, 'spki-der');
|
|
22
|
+
expect(publicKey.getCurve()).toEqual(curve);
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('encode', () => {
|
|
28
|
+
it.each(buildPublicKeyTestCases(curves))(
|
|
29
|
+
'encodes $name',
|
|
30
|
+
({ spki, curve }) => {
|
|
31
|
+
const publicKey = EcPublicKey.fromEncoded(spki, 'spki-der').encode(
|
|
32
|
+
'spki-der'
|
|
33
|
+
);
|
|
34
|
+
expect(publicKey).toEqual(spki);
|
|
35
|
+
expect(isValidPublicKey(publicKey, curve)).toEqual(true);
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
import { buildSignTestCases, isValidSignature } from 'test-commons';
|
|
3
|
+
import { EcPrivateKey, sign } from '../src';
|
|
4
|
+
import { Curve } from '@road-labs/ocmf-crypto';
|
|
5
|
+
|
|
6
|
+
const format = 'sigvalue-der';
|
|
7
|
+
const curves: Curve[] = [
|
|
8
|
+
'brainpool256r1',
|
|
9
|
+
'brainpool384r1',
|
|
10
|
+
'secp192r1',
|
|
11
|
+
'secp192k1',
|
|
12
|
+
'secp256k1',
|
|
13
|
+
'secp256r1',
|
|
14
|
+
'secp384r1',
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
describe('verify', () => {
|
|
18
|
+
it.each(buildSignTestCases(curves))(
|
|
19
|
+
'$name',
|
|
20
|
+
({ curve, data, hash, pkcs8 }) => {
|
|
21
|
+
const privateKey = EcPrivateKey.fromEncoded(pkcs8, 'pkcs8-der');
|
|
22
|
+
const signature = sign(data, privateKey, hash, format);
|
|
23
|
+
expect(isValidSignature(signature, curve)).toEqual(true);
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
import { buildVerifyTestCases } from 'test-commons';
|
|
3
|
+
import { EcPublicKey, verify } from '../src';
|
|
4
|
+
import { Curve } from '@road-labs/ocmf-crypto';
|
|
5
|
+
|
|
6
|
+
const format = 'sigvalue-der';
|
|
7
|
+
const curves: Curve[] = [
|
|
8
|
+
'brainpool256r1',
|
|
9
|
+
'brainpool384r1',
|
|
10
|
+
'secp192r1',
|
|
11
|
+
'secp192k1',
|
|
12
|
+
'secp256k1',
|
|
13
|
+
'secp256r1',
|
|
14
|
+
'secp384r1',
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
describe('verify', () => {
|
|
18
|
+
it.each(buildVerifyTestCases(curves))(
|
|
19
|
+
'$name',
|
|
20
|
+
({ curve, signature, data, hash, spki, expected }) => {
|
|
21
|
+
const publicKey = EcPublicKey.fromEncoded(spki, 'spki-der');
|
|
22
|
+
const actual = verify(signature, data, publicKey, hash, format);
|
|
23
|
+
expect(actual).toEqual(expected);
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
});
|
package/tsconfig.json
ADDED