-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #962 from oasisprotocol/xmzheng/client-sdk-callfor…
…mat-lib Xmzheng/client sdk callformat lib
- Loading branch information
Showing
9 changed files
with
326 additions
and
6 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.