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

Xmzheng/client sdk callformat lib #962

Merged
merged 12 commits into from
Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
54 changes: 53 additions & 1 deletion client-sdk/ts-web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions client-sdk/ts-web/rt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@
"scripts": {
"prepare": "tsc",
"check-playground": "cd playground && tsc -p jsconfig.json",
"fmt": "prettier --write playground/src src",
"lint": "prettier --check playground/src src",
"fmt": "prettier --write playground/src src test",
"lint": "prettier --check playground/src src test",
"playground": "cd playground && webpack s -c webpack.config.js",
"test": "jest"
},
"dependencies": {
"@oasisprotocol/client": "^0.1.1-alpha.1",
"deoxysii": "^0.0.2",
"elliptic": "^6.5.3",
"sha3": "^2.1.4"
"js-sha512": "^0.8.0",
"randombytes": "^2.0.1",
"sha3": "^2.1.4",
"tweetnacl": "^1.0.3"
},
"devDependencies": {
"@types/elliptic": "^6.4.14",
Expand Down
142 changes: 142 additions & 0 deletions client-sdk/ts-web/rt/src/callformat.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,146 @@
import * as oasis from '@oasisprotocol/client';
// @ts-expect-error missing declaration
import * as deoxysii from 'deoxysii';
import * as nacl from 'tweetnacl';
// @ts-expect-error missing declaration
import * as randomBytes from 'randombytes';

import * as mraeDeoxysii from './mrae/deoxysii';
import * as transaction from './transaction';
import * as types from './types';

/**
* Call data key pair ID domain separation context base.
*/
export const CALL_DATA_KEY_PAIR_ID_CONTEXT_BASE = 'oasis-runtime-sdk/private: tx';

/**
* EncodeConfig is call encoding configuration.
* golang: oasis-sdk/client-sdk/go/callformat/callformat.go
* rust:
*/
export interface EncodeConfig {
xmzheng marked this conversation as resolved.
Show resolved Hide resolved
/**
* publicKey is an optional runtime's call data public key to use for encrypted call formats.
*/
publicKey?: types.KeyManagerSignedPublicKey;
}

export interface MetaEncryptedX25519DeoxysII {
sk: Uint8Array;
pk: Uint8Array;
}

/**
* encodeCallWithNonceAndKeys encodes a call based on its configured call format.
* It returns the encoded call and any metadata needed to successfully decode the result.
*/
export async function encodeCallWithNonceAndKeys(
nonce: Uint8Array,
sk: Uint8Array,
pk: Uint8Array,
pro-wh marked this conversation as resolved.
Show resolved Hide resolved
call: types.Call,
format: types.CallFormat,
config?: EncodeConfig,
): Promise<[types.Call, unknown]> {
switch (format) {
case transaction.CALLFORMAT_PLAIN:
return [call, undefined];
case transaction.CALLFORMAT_ENCRYPTED_X25519DEOXYSII:
if (config?.publicKey === undefined) {
throw new Error('callformat: runtime call data public key not set');
}
const rawCall = oasis.misc.toCBOR(call);
const zeroBuffer = new Uint8Array(0);
const sealedCall = mraeDeoxysii.boxSeal(
nonce,
rawCall,
zeroBuffer,
config.publicKey.key,
sk,
);
const envelope: types.CallEnvelopeX25519DeoxysII = {
pk: pk,
nonce: nonce,
data: sealedCall,
};
const encoded: types.Call = {
format: transaction.CALLFORMAT_ENCRYPTED_X25519DEOXYSII,
method: '',
pro-wh marked this conversation as resolved.
Show resolved Hide resolved
body: oasis.misc.toCBOR(envelope),
};
const meta: MetaEncryptedX25519DeoxysII = {
sk: sk,
pk: config.publicKey.key,
};
return [encoded, meta];
default:
throw new Error(`callformat: unsupported call format: ${format}`);
}
xmzheng marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* encodeCall randomly generates nonce and keyPair and then call encodeCallWithNonceAndKeys
* It returns the encoded call and any metadata needed to successfully decode the result.
*/
export async function encodeCall(
call: types.Call,
format: types.CallFormat,
config?: EncodeConfig,
): Promise<[types.Call, unknown]> {
const nonce = randomBytes(deoxysii.NonceSize);
const keyPair = nacl.box.keyPair();
return await encodeCallWithNonceAndKeys(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

do I need to use await here? I think I can directly return the Promise, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

you're right about that. stylistically I've preferred to await inside because it makes it easier to move code around

nonce,
keyPair.secretKey,
keyPair.publicKey,
call,
format,
config,
);
}

xmzheng marked this conversation as resolved.
Show resolved Hide resolved
/**
* decodeResult performs result decoding based on the specified call format metadata.
*/
export async function decodeResult(
result: types.CallResult,
format: types.CallFormat,
meta?: MetaEncryptedX25519DeoxysII,
pro-wh marked this conversation as resolved.
Show resolved Hide resolved
): Promise<types.CallResult> {
switch (format) {
case transaction.CALLFORMAT_PLAIN:
// In case of plain-text data format, we simply pass on the result unchanged.
return result;
case transaction.CALLFORMAT_ENCRYPTED_X25519DEOXYSII:
if (result.unknown) {
if (meta) {
const envelop = oasis.misc.fromCBOR(
result.unknown,
) as types.ResultEnvelopeX25519DeoxysII;
const zeroBuffer = new Uint8Array(0);
const pt = mraeDeoxysii.boxOpen(
envelop?.nonce,
envelop?.data,
zeroBuffer,
meta.pk,
meta.sk,
);
return oasis.misc.fromCBOR(pt) as types.CallResult;
} else {
throw new Error(
`callformat: MetaEncryptedX25519DeoxysII data is required for callformat: CALLFORMAT_ENCRYPTED_X25519DEOXYSII`,
);
}
} else if (result.fail) {
throw new Error(
`callformat: failed call: module: ${result.fail.module} code: ${result.fail.code} message: ${result.fail.message}`,
);
}
throw Object.assign(new Error(`callformat: unexpected result: ${result.ok}`), {
source: result,
});
default:
throw new Error(`callformat: unsupported call format: ${format}`);
}
}
1 change: 1 addition & 0 deletions client-sdk/ts-web/rt/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * as contracts from './contracts';
export * as core from './core';
export * as event from './event';
export * as evm from './evm';
export * as mraeDeoxysii from './mrae/deoxysii';
export * as rewards from './rewards';
export * as signatureSecp256k1 from './signature_secp256k1';
export * as token from './token';
Expand Down
53 changes: 53 additions & 0 deletions client-sdk/ts-web/rt/src/mrae/deoxysii.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as oasis from '@oasisprotocol/client';
// @ts-expect-error missing declaration
import * as deoxysii from 'deoxysii';
import {sha512_256} from 'js-sha512';
import * as nacl from 'tweetnacl';

const BOX_KDF_TWEAK = 'MRAE_Box_Deoxys-II-256-128';

/**
* deriveSymmetricKey derives a MRAE AEAD symmetric key suitable for use with the asymmetric
* box primitives from the provided X25519 public and private keys.
*/

export function deriveSymmetricKey(publicKey: Uint8Array, privateKey: Uint8Array): Uint8Array {
const pmk = nacl.scalarMult(privateKey, publicKey);
var kdf = sha512_256.hmac.create(BOX_KDF_TWEAK);
kdf.update(pmk);
return new Uint8Array(kdf.arrayBuffer());
}

/**
* boxSeal boxes ("seals") the provided additional data and plaintext via
* Deoxys-II-256-128 using a symmetric key derived from the provided
* X25519 public and private keys.
*/
export function boxSeal(
nonce: Uint8Array,
plainText: Uint8Array,
associateData: Uint8Array,
publicKey: Uint8Array,
privateKey: Uint8Array,
): Uint8Array {
const sharedKey = deriveSymmetricKey(publicKey, privateKey);
var aead = new deoxysii.AEAD(sharedKey);
return aead.encrypt(nonce, plainText, associateData);
}

/**
* boxOpen unboxes ("opens") the provided additional data and plaintext via
* Deoxys-II-256-128 using a symmetric key derived from the provided
* X25519 public and private keys.
*/
export function boxOpen(
nonce: Uint8Array,
ciperText: Uint8Array,
associateData: Uint8Array,
publicKey: Uint8Array,
privateKey: Uint8Array,
): Uint8Array {
const sharedKey = deriveSymmetricKey(publicKey, privateKey);
var aead = new deoxysii.AEAD(sharedKey);
return aead.decrypt(nonce, ciperText, associateData);
}
1 change: 1 addition & 0 deletions client-sdk/ts-web/rt/src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const LATEST_TRANSACTION_VERSION = 1;
* Plain text call data.
*/
export const CALLFORMAT_PLAIN = 0;

/**
* Encrypted call data using X25519 for key exchange and Deoxys-II for symmetric encryption.
*/
Expand Down
8 changes: 6 additions & 2 deletions client-sdk/ts-web/rt/test/address.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@ describe('address', () => {
it('Should derive the address correctly', async () => {
const pk = Buffer.from('utrdHlX///////////////////////////////////8=', 'base64');
const address = await oasisRT.address.fromSigspec({ed25519: new Uint8Array(pk)});
expect(oasisRT.address.toBech32(address)).toEqual('oasis1qryqqccycvckcxp453tflalujvlf78xymcdqw4vz');
expect(oasisRT.address.toBech32(address)).toEqual(
'oasis1qryqqccycvckcxp453tflalujvlf78xymcdqw4vz',
);
});
});

describe('secp256k1eth', () => {
it('Should derive the address correctly', async () => {
const pk = Buffer.from('Arra3R5V////////////////////////////////////', 'base64');
const address = await oasisRT.address.fromSigspec({secp256k1eth: new Uint8Array(pk)});
expect(oasisRT.address.toBech32(address)).toEqual('oasis1qzd7akz24n6fxfhdhtk977s5857h3c6gf5583mcg');
expect(oasisRT.address.toBech32(address)).toEqual(
'oasis1qzd7akz24n6fxfhdhtk977s5857h3c6gf5583mcg',
);
});
});
});
Loading