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

minus tweetnacl plus webcrypto for X25519 #2036

Open
wants to merge 13 commits into
base: pro-wh/feature/formattest
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion client-sdk/go/callformat/callformat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ func TestInterop(t *testing.T) {
}
callEnc, metadata := encodeCallEncryptedX25519DeoxysII(&call, clientPK, &clientSK, nonce, &cfg)

// If these change, update runtime-sdk/src/callformat.rs too.
// If these change, update runtime-sdk/src/callformat.rs and
// client-sdk/ts-web/rt/test/callformat.test.ts too.
require.Equal(t, "a264626f6479f6666d6574686f64646d6f636b", hex.EncodeToString(cbor.Marshal(call)))
require.Equal(t, "a264626f6479a462706b5820eedc75d3c500fc1b2d321757c383e276ab705c5a02013b3f1966e9caf73cdb0264646174615823c4635f2f9496a033a578e3f1e007be5d6cfa9631fb2fe2c8c76d26b322b6afb2fa5cdf6565706f636801656e6f6e63654f00000000000000000000000000000066666f726d617401", hex.EncodeToString(cbor.Marshal(callEnc)))

Expand Down
4 changes: 1 addition & 3 deletions client-sdk/ts-web/package-lock.json

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

12 changes: 12 additions & 0 deletions client-sdk/ts-web/rt/docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,28 @@

## Unreleased changes

Breaking changes:

- `callformat.encodeCallWithNonceAndKeys` now takes a client `CryptoKeyPair`
instead of `sk` and `pk` `Uint8Array`s.
You can generate one with `mraeDeoxysii.generateKeyPair` and export its
public key with `mraeDeoxysii.publicKeyFromKeyPair`.
- `mraeDeoxysii` functions `deriveSymmetricKey`, `boxSeal`, and `boxOpen` are
now async.

New features:

- Functions that internally need to compute a hash, such as
`address.fromSigspec`, are declared as synchronous now.
- secp256k1 verification is declared as synchronous now.
- `callformat.decodeResult`'s `meta` parameter now takes `unknown`, matching
what you get from `callformat.encodeCall`.

Little things:

- We're switching lots of cryptography dependencies to noble cryptography
libraries.
- X25519 key exchange now uses the Web Crypto API.

## v1.1.0

Expand Down
3 changes: 1 addition & 2 deletions client-sdk/ts-web/rt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@
"@noble/curves": "^1.6.0",
"@noble/hashes": "^1.5.0",
"@oasisprotocol/client": "^1.1.0",
"@oasisprotocol/deoxysii": "^0.0.6",
"tweetnacl": "^1.0.3"
"@oasisprotocol/deoxysii": "^0.0.6"
},
"devDependencies": {
"@types/jest": "^29.5.13",
Expand Down
60 changes: 29 additions & 31 deletions client-sdk/ts-web/rt/src/callformat.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as oasis from '@oasisprotocol/client';
import * as deoxysii from '@oasisprotocol/deoxysii';
import * as nacl from 'tweetnacl';

import * as mraeDeoxysii from './mrae/deoxysii';
import * as transaction from './transaction';
Expand All @@ -21,35 +20,40 @@ export interface EncodeConfig {
* publicKey is an optional runtime's call data public key to use for encrypted call formats.
*/
publicKey?: types.KeyManagerSignedPublicKey;
/**
* epoch is the epoch of the ephemeral runtime key (when publicKey is set).
*/
epoch?: oasis.types.longnum;
}

export interface MetaEncryptedX25519DeoxysII {
sk: Uint8Array;
sk: CryptoKey;
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 function encodeCallWithNonceAndKeys(
export async function encodeCallWithNonceAndKeys(
nonce: Uint8Array,
sk: Uint8Array,
pk: Uint8Array,
clientKP: CryptoKeyPair,
call: types.Call,
format: types.CallFormat,
config?: EncodeConfig,
): [types.Call, unknown] {
): 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 pk = await mraeDeoxysii.publicKeyFromKeyPair(clientKP);
const sk = clientKP.privateKey;
const rawCall = oasis.misc.toCBOR(call);
const zeroBuffer = new Uint8Array(0);
const sealedCall = mraeDeoxysii.boxSeal(
const sealedCall = await mraeDeoxysii.boxSeal(
nonce,
rawCall,
zeroBuffer,
Expand All @@ -61,10 +65,12 @@ export function encodeCallWithNonceAndKeys(
nonce: nonce,
data: sealedCall,
};
if (config.epoch) {
envelope.epoch = config.epoch;
}
const encoded: types.Call = {
format: transaction.CALLFORMAT_ENCRYPTED_X25519DEOXYSII,
method: '',
body: oasis.misc.toCBOR(envelope),
body: envelope,
};
const meta: MetaEncryptedX25519DeoxysII = {
sk: sk,
Expand All @@ -80,49 +86,41 @@ export function encodeCallWithNonceAndKeys(
* 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 function encodeCall(
export async function encodeCall(
call: types.Call,
format: types.CallFormat,
config?: EncodeConfig,
): [types.Call, unknown] {
): Promise<[types.Call, unknown]> {
const nonce = new Uint8Array(deoxysii.NonceSize);
crypto.getRandomValues(nonce);
const keyPair = nacl.box.keyPair();
return encodeCallWithNonceAndKeys(
nonce,
keyPair.secretKey,
keyPair.publicKey,
call,
format,
config,
);
const clientKP = await mraeDeoxysii.generateKeyPair(true);
return await encodeCallWithNonceAndKeys(nonce, clientKP, call, format, config);
}

/**
* decodeResult performs result decoding based on the specified call format metadata.
*/
export function decodeResult(
export async function decodeResult(
result: types.CallResult,
format: types.CallFormat,
meta?: MetaEncryptedX25519DeoxysII,
): types.CallResult {
meta?: unknown,
): 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 metaEncryptedX25519DeoxysII = meta as MetaEncryptedX25519DeoxysII;
const envelope = result.unknown as types.ResultEnvelopeX25519DeoxysII;
const zeroBuffer = new Uint8Array(0);
const pt = mraeDeoxysii.boxOpen(
envelop?.nonce,
envelop?.data,
const pt = await mraeDeoxysii.boxOpen(
envelope?.nonce,
envelope?.data,
zeroBuffer,
meta.pk,
meta.sk,
metaEncryptedX25519DeoxysII.pk,
metaEncryptedX25519DeoxysII.sk,
);
return oasis.misc.fromCBOR(pt) as types.CallResult;
} else {
Expand Down
81 changes: 63 additions & 18 deletions client-sdk/ts-web/rt/src/mrae/deoxysii.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,62 @@
import {hmac} from '@noble/hashes/hmac';
import {sha512_256} from '@noble/hashes/sha512';
import * as oasis from '@oasisprotocol/client';
import * as deoxysii from '@oasisprotocol/deoxysii';
import * as nacl from 'tweetnacl';

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

export async function generateKeyPair(extractable: boolean): Promise<CryptoKeyPair> {
return (await crypto.subtle.generateKey({name: 'X25519'}, extractable, [
'deriveBits',
])) as CryptoKeyPair;
}

export async function keyPairFromPrivateKey(privateKey: Uint8Array): Promise<CryptoKeyPair> {
const privateDER = oasis.misc.concat(
new Uint8Array([
// PrivateKeyInfo
0x30, 0x2e,
// version 0
0x02, 0x01, 0x00,
// privateKeyAlgorithm 1.3.101.110
0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e,
// privateKey
0x04, 0x22, 0x04, 0x20,
]),
privateKey,
);
const privateCK = await crypto.subtle.importKey('pkcs8', privateDER, {name: 'X25519'}, true, [
'deriveBits',
]);
const privateJWK = await crypto.subtle.exportKey('jwk', privateCK);
const publicJWK = {
kty: privateJWK.kty,
crv: privateJWK.crv,
x: privateJWK.x,
} as JsonWebKey;
const publicCK = await crypto.subtle.importKey('jwk', publicJWK, {name: 'X25519'}, true, []);
return {
publicKey: publicCK,
privateKey: privateCK,
} as CryptoKeyPair;
}

export async function publicKeyFromKeyPair(keyPair: CryptoKeyPair): Promise<Uint8Array> {
return new Uint8Array(await crypto.subtle.exportKey('raw', keyPair.publicKey));
}

/**
* 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);
export async function deriveSymmetricKey(
publicKey: Uint8Array,
privateCK: CryptoKey,
): Promise<Uint8Array> {
const publicCK = await crypto.subtle.importKey('raw', publicKey, {name: 'X25519'}, true, []);
const pmk = new Uint8Array(
await crypto.subtle.deriveBits({name: 'X25519', public: publicCK}, privateCK, 256),
);
return hmac(sha512_256, BOX_KDF_TWEAK, pmk);
}

Expand All @@ -20,31 +65,31 @@ export function deriveSymmetricKey(publicKey: Uint8Array, privateKey: Uint8Array
* Deoxys-II-256-128 using a symmetric key derived from the provided
* X25519 public and private keys.
*/
export function boxSeal(
export async function boxSeal(
nonce: Uint8Array,
plainText: Uint8Array,
associateData: Uint8Array,
associatedData: Uint8Array,
publicKey: Uint8Array,
privateKey: Uint8Array,
): Uint8Array {
const sharedKey = deriveSymmetricKey(publicKey, privateKey);
var aead = new deoxysii.AEAD(sharedKey);
return aead.encrypt(nonce, plainText, associateData);
privateCK: CryptoKey,
): Promise<Uint8Array> {
const sharedKey = await deriveSymmetricKey(publicKey, privateCK);
const aead = new deoxysii.AEAD(sharedKey);
return aead.encrypt(nonce, plainText, associatedData);
}

/**
* 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(
export async function boxOpen(
nonce: Uint8Array,
ciperText: Uint8Array,
associateData: Uint8Array,
associatedData: Uint8Array,
publicKey: Uint8Array,
privateKey: Uint8Array,
): Uint8Array {
const sharedKey = deriveSymmetricKey(publicKey, privateKey);
var aead = new deoxysii.AEAD(sharedKey);
return aead.decrypt(nonce, ciperText, associateData);
privateCK: CryptoKey,
): Promise<Uint8Array> {
const sharedKey = await deriveSymmetricKey(publicKey, privateCK);
const aead = new deoxysii.AEAD(sharedKey);
return aead.decrypt(nonce, ciperText, associatedData);
}
4 changes: 2 additions & 2 deletions client-sdk/ts-web/rt/src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ export type CallHandler<BODY> = (body: BODY) => void;
export type CallHandlers = {[method: string]: CallHandler<never>};

export function visitCall(handlers: CallHandlers, call: types.Call) {
if (handlers[call.method]) {
handlers[call.method](call.body as never);
if (handlers[call.method!]) {
handlers[call.method!](call.body as never);
return true;
}
return false;
Expand Down
5 changes: 3 additions & 2 deletions client-sdk/ts-web/rt/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ export type CallFormat = number;
*/
export interface Call {
format?: CallFormat;
method: string;
method?: string;
body: unknown;
ro?: boolean;
}
Expand All @@ -252,7 +252,7 @@ export interface Call {
export interface CallResult {
ok?: unknown;
fail?: FailedCallResult;
unknown?: Uint8Array;
unknown?: unknown;
}

export interface FailedCallResult {
Expand Down Expand Up @@ -466,6 +466,7 @@ export interface EVMLogEvent {
export interface CallEnvelopeX25519DeoxysII {
pk: Uint8Array;
nonce: Uint8Array;
epoch?: oasis.types.longnum;
data: Uint8Array;
}

Expand Down
Loading