Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace node-rsa with webcrypto api (removes the need for polyfills in browser) #101

Merged
merged 39 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4cb0373
downgrade node-rsa
clangenb Apr 30, 2024
330b759
v0.12.5-alpha.0
clangenb Apr 30, 2024
b0b2d5d
upgrade node-rsa again
clangenb Apr 30, 2024
4882f08
v0.12.5-alpha.1
clangenb Apr 30, 2024
7300c70
fix: use better assert import
clangenb Apr 30, 2024
767ef32
v0.12.5-alpha.2
clangenb Apr 30, 2024
d9dc22f
change import again
clangenb Apr 30, 2024
90b0ac8
v0.12.5-alpha.3
clangenb Apr 30, 2024
7ce8b82
comment out asserts for now
clangenb Apr 30, 2024
e36887b
v0.12.5-alpha.4
clangenb Apr 30, 2024
6d4981e
checkout webcrypto api, get trusted op could not be deciphered error
clangenb May 1, 2024
6790b23
[worker] wip with webcrypto api
clangenb May 1, 2024
4bcaacc
v0.12.5-alpha.5
clangenb May 1, 2024
6ddea87
[worker] fix local import
clangenb May 1, 2024
97f5b46
v0.12.5-alpha.6
clangenb May 1, 2024
112d851
[worker] fix accessing crypto
clangenb May 1, 2024
d845766
v0.12.5-alpha.7
clangenb May 1, 2024
0902138
[worker] correctly print encrypted stuff
clangenb May 1, 2024
3ec349d
v0.12.5-alpha.8
clangenb May 1, 2024
33600bf
[worker] add some doc
clangenb May 1, 2024
d7a8117
[worker] better doc for cryptoProvider
clangenb May 1, 2024
e17fafa
[worker/webCryptoRSA] fix logging pubKey
clangenb May 1, 2024
c8f69f1
[worker] test all endianness in unit tests
clangenb May 1, 2024
b6ee1c4
Revert "[worker] test all endianness in unit tests"
clangenb May 1, 2024
0f56377
[worker] add swapEndianness function, which doesn't work
clangenb May 1, 2024
72497e9
Revert "[worker] add swapEndianness function, which doesn't work"
clangenb May 1, 2024
d1957e6
Revert "Revert "[worker] test all endianness in unit tests""
clangenb May 1, 2024
a1cf451
[worker] consistent endianness in key import
clangenb May 1, 2024
43285d9
[worker] fix: await encryption promise
clangenb May 2, 2024
e63a9b6
[worker] working encryptions for a local setup!
clangenb May 2, 2024
d02e158
[worker] improve efficiency of bit-endianness conversion
clangenb May 2, 2024
79b5676
[worker] remove tests for byte-endianness as they are unneeded
clangenb May 2, 2024
dcddc05
v0.12.5-alpha.9
clangenb May 2, 2024
1d8049e
[worker] switch to local-docker network for tests
clangenb May 2, 2024
f7a5ccf
add webcrypto notes to readme
clangenb May 2, 2024
f1b93ad
ignore tests that need a running setup
clangenb May 2, 2024
c495bd8
[worker] remove unnecessary endinanness function args from interface
clangenb May 2, 2024
39262ad
[worker] cleanup
clangenb May 2, 2024
761dc22
[worker] export to fix unused warning
clangenb May 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed .yarn/cache/asn1-npm-0.2.4-219dd49411-aa5d6f77b1.zip
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ Encointer JavaScript API monorepo

# Installation

## Crypto library
The worker uses the webcrypto api if it is run in the browser. This library is only
defined if you access the webpage with `localhost` in firefox. It is not available
on `127.0.0.1` or `0.0.0.0` due to browser security policies.


```bash
yarn add @encointer/node-api @encointer/worker-api
```
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"publishConfig": {
"directory": "build"
},
"version": "0.12.4"
"version": "0.12.5-alpha.9"
}
4 changes: 2 additions & 2 deletions packages/node-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
},
"sideEffects": false,
"type": "module",
"version": "0.12.4",
"version": "0.12.5-alpha.9",
"main": "index.js",
"dependencies": {
"@encointer/types": "^0.12.4",
"@encointer/types": "^0.12.5-alpha.9",
"@polkadot/api": "^10.9.1",
"tslib": "^2.5.3"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
},
"sideEffects": false,
"type": "module",
"version": "0.12.4",
"version": "0.12.5-alpha.9",
"main": "index.js",
"scripts": {
"generate:defs": "node --experimental-specifier-resolution=node --loader ts-node/esm ../../node_modules/.bin/polkadot-types-from-defs --package @encointer/types/interfaces --input ./src/interfaces",
Expand Down
2 changes: 1 addition & 1 deletion packages/util/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"sideEffects": false,
"type": "module",
"types": "./index.d.ts",
"version": "0.12.4",
"version": "0.12.5-alpha.9",
"main": "index.js",
"dependencies": {
"@babel/runtime": "^7.18.9",
Expand Down
4 changes: 2 additions & 2 deletions packages/util/src/assignment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { parseDegree } from "@encointer/types";
import {u64, Vec} from "@polkadot/types";
import {Option} from "@polkadot/types-codec";
import type {Moment} from "@polkadot/types/interfaces/runtime";
import assert from "assert";
// import assert from "assert";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a node-js type and it gave me some polyfill errors in the integritee-campaign page


/**
* Performs the same meetup assignment as the encointer-ceremonies pallet.
Expand Down Expand Up @@ -234,7 +234,7 @@ export function assignmentFnInverse(
}

// never observed in practice
assert(t3 >= 0, `[assignment_fn_inverse]: t3 smaller 0: ${t3}`);
// assert(t3 >= 0, `[assignment_fn_inverse]: t3 smaller 0: ${t3}`);

participants.push(t3)

Expand Down
23 changes: 11 additions & 12 deletions packages/util/src/common.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import assert from 'assert';
import type { KeyringPair } from "@polkadot/keyring/types";
import { Keyring } from "@polkadot/keyring";
import BN from "bn.js";

interface assertLengthFunc {
(upper: number, lower: number): number
}

export const assertLength: assertLengthFunc = function (upper, lower) {
const len = upper + lower;
assert(len >= 8, `Bit length can't be less than 8, provided ${len}`);
assert(len <= 128, `Bit length can't be bigger than 128, provided ${len}`);
assert(!(len & (len - 1)), `Bit length should be power of 2, provided ${len}`);
return len;
};
// interface assertLengthFunc {
// (upper: number, lower: number): number
// }
//
// export const assertLength: assertLengthFunc = function (upper, lower) {
// const len = upper + lower;
// assert(len >= 8, `Bit length can't be less than 8, provided ${len}`);
// assert(len <= 128, `Bit length can't be bigger than 128, provided ${len}`);
// assert(!(len & (len - 1)), `Bit length should be power of 2, provided ${len}`);
// return len;
// };

export interface PubKeyPinPair {
pubKey: string,
Expand Down
9 changes: 5 additions & 4 deletions packages/util/src/parserFixPoint.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import BN from "bn.js";
import assert from "assert";
// import assert from "assert";

import { assertLength } from "./common.js";
// import { assertLength } from "./common.js";

export interface ParserFixPointFn {
(raw: BN, precision?: number): number;
Expand All @@ -25,9 +25,10 @@ export interface ParserFixPointFactory {
/// raw: substrate_fixed::types::I<upper>F<lower> as I<upper+lower>
/// precision: 0..lower number bits in fractional part to process
export const parserFixPoint: ParserFixPointFactory = function (upper, lower) {
const len = assertLength(upper, lower);
// const len = assertLength(upper, lower);
const len = upper + lower;
return (raw: BN, precision: number = lower): number => {
assert(raw.bitLength() <= len, "Bit length is not equal to " + len);
// assert(raw.bitLength() <= len, "Bit length is not equal to " + len);

raw = raw.fromTwos(len);

Expand Down
11 changes: 5 additions & 6 deletions packages/util/src/toFixPoint.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import assert from 'assert';
import BN from 'bn.js';

import {assertLength, fractionalToRadix2, safeIntegerToRadix2} from './common.js';
import {fractionalToRadix2, safeIntegerToRadix2} from './common.js';

export interface ToFixPointFn {
(num: number): BN;
Expand All @@ -20,7 +19,7 @@ export interface StringToFixPointFactory {
}

export const toFixPoint: ToFixPointFactory = function (upper, lower) {
assertLength(upper, lower);
// assertLength(upper, lower);
return (num: number): BN => {
const [upperBits, lowerBits] = num.toString(2).split('.');

Expand All @@ -29,7 +28,7 @@ export const toFixPoint: ToFixPointFactory = function (upper, lower) {
};

export const stringToFixPoint: StringToFixPointFactory = function (upper, lower) {
assertLength(upper, lower);
// assertLength(upper, lower);
return (num: string): BN => {
let [integers, fractions] = num.split('.');

Expand All @@ -52,9 +51,9 @@ export const stringToFixPoint: StringToFixPointFactory = function (upper, lower)
* @param fractions_count amount of fractional bits in the fixed-point type.
*/
const toFixed = function(integers: string, fractions: string, integer_count: number, fractions_count: number): BN {
assertLength(integer_count, fractions_count);
// assertLength(integer_count, fractions_count);

assert(integers.length <= integer_count, 'Number is larger than maximum in '.concat(integer_count.toString(), 'bit'));
// assert(integers.length <= integer_count, 'Number is larger than maximum in '.concat(integer_count.toString(), 'bit'));

if (fractions !== undefined) {
const bits = integers.concat(fractions.length > fractions_count ? fractions.substring(0, fractions_count) : fractions.padEnd(fractions_count, '0'));
Expand Down
10 changes: 5 additions & 5 deletions packages/worker-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
"sideEffects": false,
"type": "module",
"types": "./index.d.ts",
"version": "0.12.4",
"version": "0.12.5-alpha.9",
"main": "index.js",
"dependencies": {
"@encointer/node-api": "^0.12.4",
"@encointer/types": "^0.12.4",
"@encointer/util": "^0.12.4",
"@learntheropes/node-rsa": "^1.1.3",
"@encointer/node-api": "^0.12.5-alpha.9",
"@encointer/types": "^0.12.5-alpha.9",
"@encointer/util": "^0.12.5-alpha.9",
"@peculiar/webcrypto": "^1.4.6",
"@polkadot/api": "^10.9.1",
"@polkadot/keyring": "^12.3.2",
"@polkadot/types": "^10.9.1",
Expand Down
3 changes: 0 additions & 3 deletions packages/worker-api/src/encointerWorker.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import type {u32, u64, Vec} from '@polkadot/types';
import {communityIdentifierFromString} from '@encointer/util';

// @ts-ignore
import NodeRSA from '@learntheropes/node-rsa';

import type {
CommunityIdentifier,
MeetupIndexType,
Expand Down
3 changes: 2 additions & 1 deletion packages/worker-api/src/integriteeWorker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,14 @@ describe('worker', () => {
describe('balance unshield should work', () => {
it('should return value', async () => {
const shard = network.chosenCid;

const result = await worker.balanceUnshieldFunds(
alice,
shard,
network.mrenclave,
alice.address,
charlie.address,
1100000000000
1100000000000,
);
console.log('balance unshield result', result.toHuman());
expect(result).toBeDefined();
Expand Down
4 changes: 0 additions & 4 deletions packages/worker-api/src/integriteeWorker.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import type {u32} from '@polkadot/types';

// @ts-ignore
import NodeRSA from '@learntheropes/node-rsa';


import type {KeyringPair} from '@polkadot/keyring/types';
import type {Balance, Hash} from '@polkadot/types/interfaces/runtime';
import type {
Expand Down
6 changes: 3 additions & 3 deletions packages/worker-api/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface IWorker extends WebSocketAsPromised {
keyring: () => Keyring | undefined;
createType: (apiType: string, obj?: any) => any;
open: () => Promise<Event>;
encrypt: (data: Uint8Array) => Vec<u8>
encrypt: (data: Uint8Array) => Promise<Vec<u8>>
registry: () => TypeRegistry
}

Expand Down Expand Up @@ -49,8 +49,8 @@ export interface PublicGetterArgs {
export type RequestArgs = PublicGetterArgs | TrustedGetterArgs | { }

export interface CallOptions {
timeout: number;
debug: boolean;
timeout?: number;
debug?: boolean;
}

export enum Request {
Expand Down
52 changes: 4 additions & 48 deletions packages/worker-api/src/parsers.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { parseI64F64 } from '@encointer/util';
import { u8aToBn } from '@polkadot/util';
import {parseI64F64} from '@encointer/util';
import {u8aToBn} from '@polkadot/util';

// @ts-ignore
import NodeRSA from '@learntheropes/node-rsa';

import type { IWorker } from './interface.js';
import type { BalanceEntry } from "@encointer/types";
import BN from "bn.js";
import type {IWorker} from './interface.js';
import type {BalanceEntry} from "@encointer/types";

export function parseBalance(self: IWorker, data: any): BalanceEntry {
const balanceEntry = self.createType('BalanceEntry<BlockNumber>', data);
Expand All @@ -23,44 +19,4 @@ export function parseBalanceType(data: any): number {
return parseI64F64(u8aToBn(data));
}

/**
* Parse a public key retrieved from the worker into `NodeRsa`.
*
* Note: This code is relatively sensitive: Changes here could lead
* to errors parsing and encryption errors in the browser, probably
* because of inconsistencies of node's `Buffer and the `buffer`
* polyfill in browser.
* @param data
*/
export function parseNodeRSA(data: any): NodeRSA {
const keyJson = JSON.parse(data);
keyJson.n = new BN(keyJson.n, 'le');
keyJson.e = new BN(keyJson.e);
const key = new NodeRSA();
setKeyOpts(key);
key.importKey({
// Important: use string here, not buffer, otherwise the browser will
// misinterpret the `n`.
n: keyJson.n.toString(10),
// Important: use number here, not buffer, otherwise the browser will
// misinterpret the `e`.
e: keyJson.e.toNumber()
}, 'components-public');
return key;
}

function setKeyOpts(key: NodeRSA) {
key.setOptions(
{
// Enforce using the pure javascript implementations by
// setting the `browser` environment, as compatibility
// with node's crypto is broken and leads to bad outputs.
environment: 'browser',
encryptionScheme: {
scheme: 'pkcs1_oaep',
hash: 'sha256',
label: ''
}
}
);
}
2 changes: 1 addition & 1 deletion packages/worker-api/src/sendRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export const sendTrustedCall = async <T>(self: IWorker, call: IntegriteeTrustedC

console.log(`TrustedOperation: ${JSON.stringify(top)}`);

const cyphertext = self.encrypt(top.toU8a());
const cyphertext = await self.encrypt(top.toU8a());

const r = self.createType(
'Request', { shard, cyphertext: cyphertext }
Expand Down
4 changes: 2 additions & 2 deletions packages/worker-api/src/testUtils/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ export const localDockerNetwork = () => {
chain: 'ws://127.0.0.1:9944',
worker: 'wss://127.0.0.1:2000',
genesisHash: '0x388c446a804e24e77ae89f5bb099edb60cacc2ac7c898ce175bdaa08629c1439',
mrenclave: 'HjkQuPjBn531Hkji2Dsj4CEYCGpqCc3aXqETMCM7x7z4',
chosenCid: 'HjkQuPjBn531Hkji2Dsj4CEYCGpqCc3aXqETMCM7x7z4',
mrenclave: '9jm9Wm4DwGxsUUPA1cvcWWxyTuynpJ2YeEcNGnm8nztk',
chosenCid: '9jm9Wm4DwGxsUUPA1cvcWWxyTuynpJ2YeEcNGnm8nztk',
customTypes: {},
palletOverrides: {}
};
Expand Down
76 changes: 76 additions & 0 deletions packages/worker-api/src/webCryptoRSA.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import BN from "bn.js";

/**
* Provides crypto the browser via the native crypto, and in the node-js environment (like our tests)
* via the `@peculiar/webcrypto` polyfill.
*/
let cryptoProvider: any;

if (typeof window !== "undefined" && typeof window.crypto !== "undefined") {
cryptoProvider = window.crypto;
} else {
const { Crypto } = require("@peculiar/webcrypto");
cryptoProvider = new Crypto();
}

/**
* Type depending on our environment browser vs. node-js.
*/
type CryptoKey = import("crypto").KeyObject | import("@peculiar/webcrypto").CryptoKey;


export async function parseWebCryptoRSA(data: any): Promise<CryptoKey> {
const keyJson = JSON.parse(data);

// Convert Base64url-encoded components to ArrayBuffer
const nArrayBuffer = new Uint8Array(new BN(keyJson.n, 'le').toArray());
const eArrayBuffer = new Uint8Array(new BN(keyJson.e, 'le').toArray());

// Import the components into CryptoKey
const publicKey = await cryptoProvider.subtle.importKey(
"jwk",
{
kty: "RSA",
e: uint8ArrayToBase64Url(eArrayBuffer),
n: uint8ArrayToBase64Url(nArrayBuffer),
ext: true,
},
{
name: "RSA-OAEP",
hash: "SHA-256",
},
true,
["encrypt"]
);

console.log(`PublicKey: ${JSON.stringify(publicKey)}`);

return publicKey;
}

export async function encryptWithPublicKey(data: Uint8Array, publicKey: CryptoKey): Promise<ArrayBuffer> {
const encryptedData = await cryptoProvider.subtle.encrypt(
{
name: "RSA-OAEP",
},
publicKey,
data
);

// console.log(`EncryptedData: ${JSON.stringify({encrypted: buf2hex(encryptedData)})}`);

return encryptedData;
}


function uint8ArrayToBase64Url(uint8Array: Uint8Array): string {
const base64String = btoa(String.fromCharCode(...uint8Array));
return base64String
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}

export function buf2hex(buffer: ArrayBuffer) { // buffer is an ArrayBuffer
return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
}
Loading
Loading