Skip to content

Commit 916c6c0

Browse files
committed
feat(gost3410): Improve mod reduction
1 parent 05d03ab commit 916c6c0

10 files changed

Lines changed: 184 additions & 24 deletions

File tree

bun.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@li0ard/gost",
3-
"version": "0.1.4",
3+
"version": "0.1.5",
44
"main": "index.js",
55
"types": "index.d.ts",
66
"type": "module",
@@ -11,6 +11,7 @@
1111
"./kuznyechik.js": "./kuznyechik/index.js",
1212
"./magma.js": "./magma/index.js",
1313
"./modes.js": "./modes/index.js",
14+
"./oids.js": "./oids/index.js",
1415
"./streebog.js": "./streebog/index.js",
1516
"./hmac.js": "./hmac.js",
1617
"./kdf.js": "./kdf.js",
@@ -33,6 +34,8 @@
3334
"docs": "typedoc --options .config/typedoc/config.cjs"
3435
},
3536
"devDependencies": {
37+
"@peculiar/asn1-schema": "^2.7.0",
38+
"@peculiar/asn1-x509": "^2.7.0",
3639
"@types/bun": "latest",
3740
"typedoc": "^0.28.19"
3841
},

src/gost3410/conversion.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ import type { AffinePoint } from "@noble/curves/abstract/curve.js";
22
import type { GostCurveParameters } from "./const.js";
33
import { Field } from "@noble/curves/abstract/modular.js";
44

5-
/**
6-
* Compute parameters (`s`, `t`) for conversion
7-
* @param curve Curve to use
8-
*/
5+
/** Compute parameters (`s`, `t`) for conversion */
96
export const computeST = (curve: GostCurveParameters): bigint[] => {
107
if(!curve.e || !curve.d) throw new Error("No Twisted Edwards parameters");
118
if(curve.st && curve.st.length != 0) return curve.st;

src/gost3410/index.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const getPublicKey = (
2020
* @param prv Private key
2121
* @param digest Digest to sign
2222
* @param rand Optional. Predefined random data for `r` and `k` generation
23-
* @returns {TRet<Uint8Array>} Concated `r` and `s`
23+
* @returns {TRet<Uint8Array>} Concated `r` and `s` (in BE order)
2424
*/
2525
export const sign = (
2626
parameters: GostCurveParameters,
@@ -31,10 +31,10 @@ export const sign = (
3131
const size = parameters.length;
3232
const curve = weierstrass(parameters);
3333
const Fn = curve.Fn;
34-
let e = Fn.fromBytes(digest);
34+
let e = mod(bytesToNumberBE(digest), Fn.ORDER); //Fn.fromBytes(digest);
3535
if(e === 0n) e = 1n;
3636

37-
const prvNum = Fn.fromBytes(prv);
37+
const prvNum = mod(bytesToNumberBE(prv), Fn.ORDER); //Fn.fromBytes(prv);
3838
while (true) {
3939
rand ||= randomBytes(size)
4040
const k = mod(bytesToNumberBE(rand), parameters.n);
@@ -64,7 +64,7 @@ export const sign = (
6464
* @param parameters Curve parameters
6565
* @param pub Public key
6666
* @param digest Digest to verify
67-
* @param signature Signature (Concated `r` and `s`)
67+
* @param signature Signature (Concated `r` and `s`) (in BE order)
6868
*/
6969
export const verify = (
7070
parameters: GostCurveParameters,
@@ -82,7 +82,7 @@ export const verify = (
8282
const s = bytesToNumberBE(signature.subarray(size));
8383

8484
if(r <= 0 || r >= parameters.n || s <= 0 || s >= parameters.n) return false;
85-
let e = Fn.fromBytes(digest);
85+
let e = mod(bytesToNumberBE(digest), Fn.ORDER); //Fn.fromBytes(digest);
8686
if(e === 0n) e = 1n;
8787

8888
const v = Fn.inv(e);
@@ -97,17 +97,11 @@ export const verify = (
9797
}
9898

9999
/** Swap `r` and `s` in signature */
100-
export const swapSignature = (curve: GostCurveParameters, signature: TArg<Uint8Array>): TRet<Uint8Array> => concatBytes(
101-
signature.subarray(curve.length),
102-
signature.subarray(0, curve.length),
100+
export const swapPoint = (curve: GostCurveParameters, point: TArg<Uint8Array>): TRet<Uint8Array> => concatBytes(
101+
point.subarray(curve.length),
102+
point.subarray(0, curve.length),
103103
);
104104

105-
/** Get curve parameters by OID */
106-
export const getCurveByOid = (oid: string): GostCurveParameters | undefined => {
107-
for (const [_, params] of Object.entries(CURVES))
108-
if (params.oids?.includes(oid)) return params;
109-
}
110-
111105
export * from "./const.js";
112106
export * from "./vko.js";
113107
export * from "./conversion.js";

src/gost3410/vko.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import { bytesToNumberBE, concatBytes, numberToBytesLE, type TArg, type TRet } f
1010
* @param parameters Curve parameters
1111
* @param prv Private key
1212
* @param pub Public key
13-
* @param ukm User keying material (aka salt)
14-
* @returns {TRet<Uint8Array>} Shared key
13+
* @param ukm User keying material (aka salt, VKO-factor)
14+
* @returns {TRet<Uint8Array>} Shared key (Not hashed)
1515
*/
1616
export const kek = (
1717
parameters: GostCurveParameters,
@@ -31,7 +31,7 @@ export const kek = (
3131
* @param parameters Curve parameters
3232
* @param prv Private key
3333
* @param pub Public key
34-
* @param ukm User keying material (aka salt)
34+
* @param ukm User keying material (aka salt, VKO-factor)
3535
* @returns {TRet<Uint8Array>} Shared key
3636
*/
3737
export const kek_34102001 = (
@@ -46,7 +46,7 @@ export const kek_34102001 = (
4646
* @param parameters Curve parameters
4747
* @param prv Private key
4848
* @param pub Public key
49-
* @param ukm User keying material (aka salt)
49+
* @param ukm User keying material (aka salt, VKO-factor)
5050
* @returns {TRet<Uint8Array>} Shared key
5151
*/
5252
export const kek_34102012256 = (
@@ -61,7 +61,7 @@ export const kek_34102012256 = (
6161
* @param parameters Curve parameters
6262
* @param prv Private key
6363
* @param pub Public key
64-
* @param ukm User keying material (aka salt)
64+
* @param ukm User keying material (aka salt, VKO-factor)
6565
* @returns {TRet<Uint8Array>} Shared key
6666
*/
6767
export const kek_34102012512 = (

src/gost3410/x509.spec.ts

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { concatBytes, hexToBytes, type TArg } from "@noble/hashes/utils.js";
2+
import { test, expect } from "bun:test";
3+
import { getPublicKey, swapPoint, verify } from ".";
4+
import { AsnConvert, AsnProp, AsnPropTypes, OctetString } from "@peculiar/asn1-schema";
5+
import { Certificate } from "@peculiar/asn1-x509";
6+
import { getCurveByOid, getHashByOid } from "../oids";
7+
8+
class PublicKeyParameters {
9+
@AsnProp({ type: AsnPropTypes.ObjectIdentifier })
10+
curve!: string;
11+
12+
@AsnProp({ type: AsnPropTypes.ObjectIdentifier })
13+
digest!: string;
14+
}
15+
16+
const _4 = new Uint8Array([4]);
17+
18+
const proceedCertitificate = (
19+
privateKey: TArg<Uint8Array>,
20+
certificate: TArg<Uint8Array>,
21+
checkPublicKey: boolean = true
22+
) => {
23+
const parsed = AsnConvert.parse(certificate, Certificate);
24+
const parameters = AsnConvert.parse(parsed.tbsCertificate.subjectPublicKeyInfo.algorithm.parameters!, PublicKeyParameters);
25+
26+
const curve = getCurveByOid(parameters.curve);
27+
if(!curve) throw new Error("Invalid curve parameters");
28+
29+
const digest = getHashByOid(parameters.digest);
30+
if(!digest) throw new Error("Invalid hash function");
31+
32+
// Unfortunately, GOST doesn't have unificated serialization
33+
const spk = swapPoint(
34+
curve,
35+
new Uint8Array(
36+
AsnConvert.parse(parsed.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey, OctetString).buffer
37+
).reverse()
38+
);
39+
40+
if(checkPublicKey) {
41+
const pk = getPublicKey(curve, privateKey);
42+
// Unfortunately, GOST doesn't have unificated serialization
43+
expect(pk.subarray(1)).toStrictEqual(spk);
44+
}
45+
46+
expect(verify(
47+
curve,
48+
concatBytes(_4, spk),
49+
digest(new Uint8Array(parsed.tbsCertificateRaw!)).reverse(),
50+
swapPoint(curve, new Uint8Array(parsed.signatureValue))
51+
)).toBeTrue();
52+
}
53+
54+
test("[X.509] GOST R 34.10-2012 256 bit", () => {
55+
const privateKey = hexToBytes("BFCF1D623E5CDD3032A7C6EABB4A923C46E43D640FFEAAF2C3ED39A8FA399924");
56+
const certificate = `
57+
MIICYjCCAg+gAwIBAgIBATAKBggqhQMHAQEDAjBWMSkwJwYJKoZIhvcNAQkBFhpH
58+
b3N0UjM0MTAtMjAxMkBleGFtcGxlLmNvbTEpMCcGA1UEAxMgR29zdFIzNDEwLTIw
59+
MTIgKDI1NiBiaXQpIGV4YW1wbGUwHhcNMTMxMTA1MTQwMjM3WhcNMzAxMTAxMTQw
60+
MjM3WjBWMSkwJwYJKoZIhvcNAQkBFhpHb3N0UjM0MTAtMjAxMkBleGFtcGxlLmNv
61+
bTEpMCcGA1UEAxMgR29zdFIzNDEwLTIwMTIgKDI1NiBiaXQpIGV4YW1wbGUwZjAf
62+
BggqhQMHAQEBATATBgcqhQMCAiQABggqhQMHAQECAgNDAARAut/Qw1MUq9KPqkdH
63+
C2xAF3K7TugHfo9n525D2s5mFZdD5pwf90/i4vF0mFmr9nfRwMYP4o0Pg1mOn5Rl
64+
aXNYraOBwDCBvTAdBgNVHQ4EFgQU1fIeN1HaPbw+XWUzbkJ+kHJUT0AwCwYDVR0P
65+
BAQDAgHGMA8GA1UdEwQIMAYBAf8CAQEwfgYDVR0BBHcwdYAU1fIeN1HaPbw+XWUz
66+
bkJ+kHJUT0ChWqRYMFYxKTAnBgkqhkiG9w0BCQEWGkdvc3RSMzQxMC0yMDEyQGV4
67+
YW1wbGUuY29tMSkwJwYDVQQDEyBHb3N0UjM0MTAtMjAxMiAoMjU2IGJpdCkgZXhh
68+
bXBsZYIBATAKBggqhQMHAQEDAgNBAF5bm4BbARR6hJLEoWJkOsYV3Hd7kXQQjz3C
69+
dqQfmHrz6TI6Xojdh/t8ckODv/587NS5/6KsM77vc6Wh90NAT2s=
70+
`;
71+
72+
proceedCertitificate(privateKey, Uint8Array.fromBase64(certificate));
73+
});
74+
75+
test("[X.509] GOST R 34.10-2012 512 bit", () => {
76+
const privateKey = hexToBytes("3FC01CDCD4EC5F972EB482774C41E66DB7F380528DFE9E67992BA05AEE462435757530E641077CE587B976C8EEB48C48FD33FD175F0C7DE6A44E014E6BCB074B");
77+
const certificate = `
78+
MIIC6DCCAlSgAwIBAgIBATAKBggqhQMHAQEDAzBWMSkwJwYJKoZIhvcNAQkBFhpH
79+
b3N0UjM0MTAtMjAxMkBleGFtcGxlLmNvbTEpMCcGA1UEAxMgR29zdFIzNDEwLTIw
80+
MTIgKDUxMiBiaXQpIGV4YW1wbGUwHhcNMTMxMDA0MDczNjA0WhcNMzAxMDAxMDcz
81+
NjA0WjBWMSkwJwYJKoZIhvcNAQkBFhpHb3N0UjM0MTAtMjAxMkBleGFtcGxlLmNv
82+
bTEpMCcGA1UEAxMgR29zdFIzNDEwLTIwMTIgKDUxMiBiaXQpIGV4YW1wbGUwgaow
83+
IQYIKoUDBwEBAQIwFQYJKoUDBwECAQICBggqhQMHAQECAwOBhAAEgYATGQ9VCiM5
84+
FRGCQ8MEz2F1dANqhaEuywa8CbxOnTvaGJpFQVXQwkwvLFAKh7hk542vOEtxpKtT
85+
CXfGf84nRhMH/Q9bZeAc2eO/yhxrsQhTBufa1Fuou2oe/jUOaG6RAtUUvRzhNTpp
86+
RGGl1+EIY2vzzUua9j9Ol/gAoy/LNKQIfqOBwDCBvTAdBgNVHQ4EFgQUPcbTRXJZ
87+
nHtjj+eBP7b5lcTMekIwCwYDVR0PBAQDAgHGMA8GA1UdEwQIMAYBAf8CAQEwfgYD
88+
VR0BBHcwdYAUPcbTRXJZnHtjj+eBP7b5lcTMekKhWqRYMFYxKTAnBgkqhkiG9w0B
89+
CQEWGkdvc3RSMzQxMC0yMDEyQGV4YW1wbGUuY29tMSkwJwYDVQQDEyBHb3N0UjM0
90+
MTAtMjAxMiAoNTEyIGJpdCkgZXhhbXBsZYIBATAKBggqhQMHAQEDAwOBgQBObS7o
91+
ppPTXzHyVR1DtPa8b57nudJzI4czhsfeX5HDntOq45t9B/qSs8dC6eGxbhHZ9zCO
92+
SFtxWYdmg0au8XI9Xb8vTC1qdwWID7FFjMWDNQZb6lYh/J+8F2xKylvB5nIlRZqO
93+
o3eUNFkNyHJwQCk2WoOlO16zwGk2tdKH4KmD5w==
94+
`;
95+
96+
proceedCertitificate(privateKey, Uint8Array.fromBase64(certificate));
97+
});
98+
99+
test("[X.509] GOST R 34.10-2012 (Root CA)", () => {
100+
// ПАК «Головной удостоверяющий центр» -> серийный номер: 4E6D478B26F27D657F768E025CE3D393
101+
// https://e-trust.gosuslugi.ru/app/scc/portal/api/v1/portal/ca/download/4BC6DC14D97010C41A26E058AD851F81C842415A
102+
const certificate = `
103+
MIIFFDCCBMGgAwIBAgIQTm1HiybyfWV/do4CXOPTkzAKBggqhQMHAQEDAjCCASQxHjAcBgkqhkiG9w0B
104+
CQEWD2RpdEBtaW5zdnlhei5ydTELMAkGA1UEBhMCUlUxGDAWBgNVBAgMDzc3INCc0L7RgdC60LLQsDEZ
105+
MBcGA1UEBwwQ0LMuINCc0L7RgdC60LLQsDEuMCwGA1UECQwl0YPQu9C40YbQsCDQotCy0LXRgNGB0LrQ
106+
sNGPLCDQtNC+0LwgNzEsMCoGA1UECgwj0JzQuNC90LrQvtC80YHQstGP0LfRjCDQoNC+0YHRgdC40Lgx
107+
GDAWBgUqhQNkARINMTA0NzcwMjAyNjcwMTEaMBgGCCqFAwOBAwEBEgwwMDc3MTA0NzQzNzUxLDAqBgNV
108+
BAMMI9Cc0LjQvdC60L7QvNGB0LLRj9C30Ywg0KDQvtGB0YHQuNC4MB4XDTE4MDcwNjEyMTgwNloXDTM2
109+
MDcwMTEyMTgwNlowggEkMR4wHAYJKoZIhvcNAQkBFg9kaXRAbWluc3Z5YXoucnUxCzAJBgNVBAYTAlJV
110+
MRgwFgYDVQQIDA83NyDQnNC+0YHQutCy0LAxGTAXBgNVBAcMENCzLiDQnNC+0YHQutCy0LAxLjAsBgNV
111+
BAkMJdGD0LvQuNGG0LAg0KLQstC10YDRgdC60LDRjywg0LTQvtC8IDcxLDAqBgNVBAoMI9Cc0LjQvdC6
112+
0L7QvNGB0LLRj9C30Ywg0KDQvtGB0YHQuNC4MRgwFgYFKoUDZAESDTEwNDc3MDIwMjY3MDExGjAYBggq
113+
hQMDgQMBARIMMDA3NzEwNDc0Mzc1MSwwKgYDVQQDDCPQnNC40L3QutC+0LzRgdCy0Y/Qt9GMINCg0L7R
114+
gdGB0LjQuDBmMB8GCCqFAwcBAQEBMBMGByqFAwICIwEGCCqFAwcBAQICA0MABEB1OSpFp7milX33EP0i
115+
kge6HbZacYp9fVj8sUa5RWFXrB27SKX5SvtIGepqKev69RSYeHHKR+jT9YX2NuSK9wONo4IBwjCCAb4w
116+
gfUGBSqFA2RwBIHrMIHoDDTQn9CQ0JrQnCDCq9Ca0YDQuNC/0YLQvtCf0YDQviBIU03CuyDQstC10YDR
117+
gdC40LggMi4wDEPQn9CQ0JogwqvQk9C+0LvQvtCy0L3QvtC5INGD0LTQvtGB0YLQvtCy0LXRgNGP0Y7R
118+
idC40Lkg0YbQtdC90YLRgMK7DDXQl9Cw0LrQu9GO0YfQtdC90LjQtSDihJYgMTQ5LzMvMi8yLzIzINC+
119+
0YIgMDIuMDMuMjAxOAw00JfQsNC60LvRjtGH0LXQvdC40LUg4oSWIDE0OS83LzYvMTA1INC+0YIgMjcu
120+
MDYuMjAxODA/BgUqhQNkbwQ2DDTQn9CQ0JrQnCDCq9Ca0YDQuNC/0YLQvtCf0YDQviBIU03CuyDQstC1
121+
0YDRgdC40LggMi4wMEMGA1UdIAQ8MDowCAYGKoUDZHEBMAgGBiqFA2RxAjAIBgYqhQNkcQMwCAYGKoUD
122+
ZHEEMAgGBiqFA2RxBTAGBgRVHSAAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
123+
DgQWBBTCVPG0a9RMt+BtNrQjkPH+wzybBjAKBggqhQMHAQEDAgNBAJr6/eI7rHL7+FsQnoH2i6DVxqal
124+
bIxLKj05edpZGPLLb6B2PTAMya7pSt9hb8QnFABgsR4IE5gT4VVkDWbX/n4=
125+
`
126+
proceedCertitificate(new Uint8Array(), Uint8Array.fromBase64(certificate), false);
127+
});

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from "./gost341194/index.js";
33
export * from "./kuznyechik/index.js";
44
export * from "./magma/index.js";
55
export * from "./modes/index.js";
6+
export * from "./oids/index.js";
67
export * from "./streebog/index.js";
78
export * from "./hmac.js";
89
export * from "./kdf.js";

src/oids/index.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { CURVES, type GostCurveParameters } from "../gost3410/const.js";
2+
import { gost341194 } from "../gost341194/index.js";
3+
import { streebog256, streebog512 } from "../streebog/index.js";
4+
import type { HashFunctionWrapper } from "../types.js";
5+
6+
const HASHES_OID: Record<string, HashFunctionWrapper> = {
7+
"1.2.643.7.1.1.2.1": gost341194,
8+
"1.2.643.7.1.1.2.2": streebog256,
9+
"1.2.643.7.1.1.2.3": streebog512
10+
}
11+
12+
/** Get curve parameters by OID */
13+
export const getCurveByOid = (oid: string): GostCurveParameters | undefined => {
14+
for (const [_, params] of Object.entries(CURVES))
15+
if (params.oids?.includes(oid)) return params;
16+
}
17+
18+
/** Get hash function by OID */
19+
export const getHashByOid = (oid: string): HashFunctionWrapper | undefined => HASHES_OID[oid];

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { TArg, TRet } from "@noble/hashes/utils.js";
22

3+
export type HashFunctionWrapper = (msg: TArg<Uint8Array>) => TRet<Uint8Array>;
4+
35
/** Cipher core */
46
export type Cipher = {
57
/** Block size */

tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"jsx": "react-jsx",
99
"allowJs": true,
1010
"types": ["bun"],
11+
"experimentalDecorators": true,
1112

1213
// Bundler mode
1314
"moduleResolution": "bundler",

0 commit comments

Comments
 (0)