Skip to content

Commit

Permalink
Merge pull request #962 from oasisprotocol/xmzheng/client-sdk-callfor…
Browse files Browse the repository at this point in the history
…mat-lib

Xmzheng/client sdk callformat lib
  • Loading branch information
xmzheng authored Jul 6, 2022
2 parents b4efbe8 + ed84b41 commit 84c1789
Show file tree
Hide file tree
Showing 9 changed files with 326 additions and 6 deletions.
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 {
/**
* 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,
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: '',
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}`);
}
}

/**
* 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(
nonce,
keyPair.secretKey,
keyPair.publicKey,
call,
format,
config,
);
}

/**
* decodeResult performs result decoding based on the specified call format metadata.
*/
export async function decodeResult(
result: types.CallResult,
format: types.CallFormat,
meta?: MetaEncryptedX25519DeoxysII,
): 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

0 comments on commit 84c1789

Please sign in to comment.