diff --git a/CHANGELOG.md b/CHANGELOG.md index 14dbc3cfe..7e2b5d06f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to casper-js-sdk. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 2.5.1 + +### Fixed + +- Added stronger validation to `PublicKey.fromHex` method +- Fix for deploy's `execution_result` type signatures +- Fix instanceof problem in `CLValueParser` which caused problems when two different versions of SDK was used in one project +- Signer methods fixes + ## 2.5.0 ### Added diff --git a/package-lock.json b/package-lock.json index 5f3db0b5d..708f5576a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "casper-js-sdk", - "version": "2.4.0", + "version": "2.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ef2ad65a0..98b84553e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "casper-js-sdk", - "version": "2.5.0", + "version": "2.5.1", "license": "Apache 2.0", "description": "SDK to interact with the Casper blockchain", "homepage": "https://github.com/casper-ecosystem/casper-js-sdk#README.md", diff --git a/src/@types/casperlabsSigner.d.ts b/src/@types/casperlabsSigner.d.ts index 6b0c7c378..f0fc3ea3c 100644 --- a/src/@types/casperlabsSigner.d.ts +++ b/src/@types/casperlabsSigner.d.ts @@ -1,3 +1,5 @@ +import { JsonTypes } from 'typedjson'; + interface CasperLabsHelper { /** * Returns Signer version @@ -22,10 +24,21 @@ interface CasperLabsHelper { * @param targetPublicKeyHex public key in hex format with algorithm prefix. Used to display hex-formatted address on the UI */ sign: ( - deploy: any, + deploy: { deploy: JsonTypes }, sourcePublicKeyHex: string, targetPublicKeyHex: string - ) => Promise; + ) => Promise<{ deploy: JsonTypes }>; + + /** + * Send raw string message to Signer for signing. + * @param message string to be signed. + * @param signingPublicKey public key in hex format, the corresponding secret key (from the vault) will be used to sign. + * @returns `Base16` signature + */ + signMessage: ( + rawMessage: string, + signingPublicKey: string + ) => Promise; /* * Returns base64 encoded public key of user current selected account. diff --git a/src/lib/CLValue/PublicKey.ts b/src/lib/CLValue/PublicKey.ts index 4eafea93b..7d9eebc7e 100644 --- a/src/lib/CLValue/PublicKey.ts +++ b/src/lib/CLValue/PublicKey.ts @@ -166,6 +166,9 @@ export class CLPublicKey extends CLValue { if (publicKeyHex.length < 2) { throw new Error('Asymmetric key error: too short'); } + if (!/^0(1[0-9a-f]{64}|2[0-9a-f]{66})$/.test(publicKeyHex)) { + throw new Error('Invalid public key'); + } const publicKeyHexBytes = decodeBase16(publicKeyHex); return new CLPublicKey(publicKeyHexBytes.subarray(1), publicKeyHexBytes[0]); diff --git a/src/lib/CLValue/utils.ts b/src/lib/CLValue/utils.ts index 62a32d389..0d85b8360 100644 --- a/src/lib/CLValue/utils.ts +++ b/src/lib/CLValue/utils.ts @@ -36,7 +36,6 @@ import { CLOptionBytesParser, CLResultType, CLResultBytesParser, - CLTupleType, CLTuple1Type, CLTuple2Type, CLTuple3Type, @@ -157,67 +156,68 @@ export const matchTypeToCLType = (type: any): CLType => { export const matchByteParserByCLType = ( val: CLType ): Result => { - if (val instanceof CLBoolType) { + if (val.tag === CLTypeTag.Bool) { return Ok(new CLBoolBytesParser()); } - if (val instanceof CLI32Type) { + if (val.tag === CLTypeTag.I32) { return Ok(new CLI32BytesParser()); } - if (val instanceof CLI64Type) { + if (val.tag === CLTypeTag.I64) { return Ok(new CLI64BytesParser()); } - if (val instanceof CLU8Type) { + if (val.tag === CLTypeTag.U8) { return Ok(new CLU8BytesParser()); } - if (val instanceof CLU32Type) { + if (val.tag === CLTypeTag.U32) { return Ok(new CLU32BytesParser()); } - if (val instanceof CLU64Type) { + if (val.tag === CLTypeTag.U64) { return Ok(new CLU64BytesParser()); } - if (val instanceof CLU128Type) { + if (val.tag === CLTypeTag.U128) { return Ok(new CLU128BytesParser()); } - if (val instanceof CLU256Type) { + if (val.tag === CLTypeTag.U256) { return Ok(new CLU256BytesParser()); } - if (val instanceof CLU512Type) { + if (val.tag === CLTypeTag.U512) { return Ok(new CLU512BytesParser()); } - if (val instanceof CLByteArrayType) { + if (val.tag === CLTypeTag.ByteArray) { return Ok(new CLByteArrayBytesParser()); } - if (val instanceof CLByteArrayType) { - return Ok(new CLByteArrayBytesParser()); - } - if (val instanceof CLURefType) { + if (val.tag === CLTypeTag.URef) { return Ok(new CLURefBytesParser()); } - if (val instanceof CLKeyType) { + if (val.tag === CLTypeTag.Key) { return Ok(new CLKeyBytesParser()); } - if (val instanceof CLPublicKeyType) { + if (val.tag === CLTypeTag.PublicKey) { return Ok(new CLPublicKeyBytesParser()); } - if (val instanceof CLListType) { + if (val.tag === CLTypeTag.List) { return Ok(new CLListBytesParser()); } - if (val instanceof CLMapType) { + if (val.tag === CLTypeTag.Map) { return Ok(new CLMapBytesParser()); } - if (val instanceof CLTupleType) { + if ( + val.tag === CLTypeTag.Tuple1 || + val.tag === CLTypeTag.Tuple2 || + val.tag === CLTypeTag.Tuple3 + ) { return Ok(new CLTupleBytesParser()); } - if (val instanceof CLOptionType) { + if (val.tag === CLTypeTag.Option) { return Ok(new CLOptionBytesParser()); } - if (val instanceof CLResultType) { + if (val.tag === CLTypeTag.Result) { return Ok(new CLResultBytesParser()); } - if (val instanceof CLStringType) { + if (val.tag === CLTypeTag.String) { return Ok(new CLStringBytesParser()); } - if (val instanceof CLUnitType) { + if (val.tag === CLTypeTag.Unit) { return Ok(new CLUnitBytesParser()); } return Err('Unknown type'); diff --git a/src/lib/SignedMessage.ts b/src/lib/SignedMessage.ts index f21838844..a72945bbd 100644 --- a/src/lib/SignedMessage.ts +++ b/src/lib/SignedMessage.ts @@ -5,18 +5,40 @@ import { sha256 } from 'ethereum-cryptography/sha256'; import { CLPublicKey } from './CLValue/'; import { AsymmetricKey } from './Keys'; +/** + * Method for formatting messages with Casper header. + * @param message The string to be formatted. + * @returns The bytes of the formatted message + */ +export const formatMessageWithHeaders = (message: string): Uint8Array => { + // Avoiding usage of Text Encoder lib to support legacy nodejs versions. + return Uint8Array.from(Buffer.from(`Casper Message:\n${message}`)); +}; + /** * Method for signing string message. * @param key AsymmetricKey used to sign the message * @param message Message that will be signed * @return Uint8Array Signature in byte format */ -export const signMessage = ( +export const signRawMessage = ( key: AsymmetricKey, message: string ): Uint8Array => { - const messageWithHeader = Buffer.from(`Casper Message:\n${message}`); - return key.sign(messageWithHeader); + return key.sign(formatMessageWithHeaders(message)); +}; + +/** + * Method for signing formatted message in bytes format. + * @param key AsymmetricKey used to sign the message + * @param formattedMessageBytes Bytes of the formatted message. (Strings can be formatted using the `formatMessageWithHeaders()` method) + * @returns Uint8Array Signature in byte format + */ +export const signFormattedMessage = ( + key: AsymmetricKey, + formattedMessageBytes: Uint8Array +): Uint8Array => { + return key.sign(formattedMessageBytes); }; /** @@ -31,14 +53,14 @@ export const verifyMessageSignature = ( message: string, signature: Uint8Array ): boolean => { - const messageWithHeader = Buffer.from(`Casper Message:\n${message}`); + const messageWithHeader = formatMessageWithHeaders(message); if (key.isEd25519()) { return nacl.sign_detached_verify(messageWithHeader, signature, key.value()); } if (key.isSecp256K1()) { return secp256k1.ecdsaVerify( signature, - sha256(messageWithHeader), + sha256(Buffer.from(messageWithHeader)), key.value() ); } diff --git a/src/lib/Signer.ts b/src/lib/Signer.ts index 6ce2bcbd4..f61063001 100644 --- a/src/lib/Signer.ts +++ b/src/lib/Signer.ts @@ -1,42 +1,79 @@ /** - * Provide methods to communicate with [CasperLabs Signer Extension](https://github.com/CasperLabs/signer). + * Provide methods to communicate with [CasperLabs Signer Extension](https://github.com/casper-ecosystem/signer). * Works only on browser. * * @packageDocumentation */ +import { JsonTypes } from 'typedjson'; +import { + CasperLabsHelper, + SignerTestingHelper +} from '../@types/casperlabsSigner'; + +declare global { + interface Window { + casperlabsHelper: CasperLabsHelper; + signerTestingHelper: SignerTestingHelper; + } +} + +const helperPresent = () => { + return !(typeof window.casperlabsHelper === 'undefined'); +}; + /** * Returns Signer version */ export const getVersion: () => Promise = async () => { - try { - return await window.casperlabsHelper!.getVersion(); - } catch { - return '<1.0.0'; + if (helperPresent()) { + try { + return await window.casperlabsHelper.getVersion(); + } catch { + return '<1.0.0'; + } } + return Promise.reject( + new Error( + 'Content script not found - make sure you have the Signer installed and refresh the page before trying again.' + ) + ); }; /** * Check whether CasperLabs Signer extension is connected */ export const isConnected: () => Promise = async () => { - return await window.casperlabsHelper!.isConnected(); + if (helperPresent()) return await window.casperlabsHelper.isConnected(); + return Promise.reject( + new Error( + 'Content script not found - make sure you have the Signer installed and refresh the page before trying again.' + ) + ); }; /** * Attempt connection to Signer */ export const sendConnectionRequest: () => void = () => { - return window.casperlabsHelper!.requestConnection(); + if (helperPresent()) return window.casperlabsHelper.requestConnection(); + throw new Error( + 'Content script not found - make sure you have the Signer installed and refresh the page before trying again.' + ); }; /** - * Return base64 encoded public key of user current selected account. + * **Deprecated** in favour of `getActivePublicKey()`. + * Returns `base64` encoded public key of currently selected account. * * @throws Error if haven't connected to CasperLabs Signer browser extension. */ export const getSelectedPublicKeyBase64: () => Promise = () => { - return window.casperlabsHelper!.getSelectedPublicKeyBase64(); + if (helperPresent()) + return window.casperlabsHelper.getSelectedPublicKeyBase64(); + throw new Error( + 'Content script not found - make sure you have the Signer installed and refresh the page before trying again.' + ); }; /** @@ -45,7 +82,12 @@ export const getSelectedPublicKeyBase64: () => Promise = () => { * @returns {string} Hex-encoded public key with algorithm prefix. */ export const getActivePublicKey: () => Promise = () => { - return window.casperlabsHelper!.getActivePublicKey(); + if (helperPresent()) return window.casperlabsHelper.getActivePublicKey(); + return Promise.reject( + new Error( + 'Content script not found - make sure you have the Signer installed and refresh the page before trying again.' + ) + ); }; /** @@ -60,18 +102,37 @@ export const getActivePublicKey: () => Promise = () => { * @throws Error if targetPublicKeyHex is not the same as the key that is used as target in deploy. */ export const sign: ( - deploy: any, + deploy: { deploy: JsonTypes }, sourcePublicKey: string, targetPublicKey: string -) => Promise = ( - deploy: any, +) => Promise<{ deploy: JsonTypes }> = ( + deploy: { deploy: JsonTypes }, sourcePublicKey: string, targetPublicKey: string ) => { - return window.casperlabsHelper!.sign( - deploy, - sourcePublicKey, - targetPublicKey + if (helperPresent()) + return window.casperlabsHelper.sign( + deploy, + sourcePublicKey, + targetPublicKey + ); + return Promise.reject( + new Error( + 'Content script not found - make sure you have the Signer installed and refresh the page before trying again.' + ) + ); +}; + +export const signMessage: ( + message: string, + signingPublicKey: string +) => Promise = (message: string, signingPublicKey: string) => { + if (helperPresent()) + return window.casperlabsHelper.signMessage(message, signingPublicKey); + return Promise.reject( + new Error( + 'Content script not found - make sure you have the Signer installed and refresh the page before trying again.' + ) ); }; @@ -79,44 +140,91 @@ export const sign: ( * Forces Signer to disconnect from the currently open site. */ export const disconnectFromSite: () => void = () => { - return window.casperlabsHelper!.disconnectFromSite(); + if (helperPresent()) return window.casperlabsHelper.disconnectFromSite(); + return Promise.reject( + new Error( + 'Content script not found - make sure you have the Signer installed and refresh the page before trying again.' + ) + ); }; export const forceConnection: () => void = () => { - return window.signerTestingHelper!.forceConnection(); + if (helperPresent()) return window.signerTestingHelper.forceConnection(); + return Promise.reject( + new Error( + 'Content script not found - make sure you have the Signer installed and refresh the page before trying again.' + ) + ); }; export const forceDisconnect: () => void = () => { - return window.signerTestingHelper!.forceDisconnect(); + if (helperPresent()) return window.signerTestingHelper.forceDisconnect(); + return Promise.reject( + new Error( + 'Content script not found - make sure you have the Signer installed and refresh the page before trying again.' + ) + ); }; export const hasCreatedVault: () => Promise = () => { - return window.signerTestingHelper!.hasCreatedVault(); + if (helperPresent()) return window.signerTestingHelper.hasCreatedVault(); + return Promise.reject( + new Error( + 'Content script not found - make sure you have the Signer installed and refresh the page before trying again.' + ) + ); }; export const resetExistingVault: () => Promise = () => { - return window.signerTestingHelper!.resetExistingVault(); + if (helperPresent()) return window.signerTestingHelper.resetExistingVault(); + return Promise.reject( + new Error( + 'Content script not found - make sure you have the Signer installed and refresh the page before trying again.' + ) + ); }; export const createNewVault: (password: string) => Promise = ( password: string ) => { - return window.signerTestingHelper!.createNewVault(password); + if (helperPresent()) + return window.signerTestingHelper.createNewVault(password); + return Promise.reject( + new Error( + 'Content script not found - make sure you have the Signer installed and refresh the page before trying again.' + ) + ); }; export const createTestAccount: ( name: string, privateKey: string ) => Promise = (name: string, privateKey: string) => { - return window.signerTestingHelper!.createTestAccount(name, privateKey); + if (helperPresent()) + return window.signerTestingHelper.createTestAccount(name, privateKey); + return Promise.reject( + new Error( + 'Content script not found - make sure you have the Signer installed and refresh the page before trying again.' + ) + ); }; export const getToSignMessageID: () => Promise = () => { - return window.signerTestingHelper!.getToSignMessageID(); + if (helperPresent()) return window.signerTestingHelper.getToSignMessageID(); + return Promise.reject( + new Error( + 'Content script not found - make sure you have the Signer installed and refresh the page before trying again.' + ) + ); }; export const signTestDeploy: (msgId: number) => Promise = ( msgId: number ) => { - return window.signerTestingHelper!.signTestDeploy(msgId); + if (helperPresent()) return window.signerTestingHelper.signTestDeploy(msgId); + return Promise.reject( + new Error( + 'Content script not found - make sure you have the Signer installed and refresh the page before trying again.' + ) + ); }; diff --git a/src/services/CasperServiceByJsonRPC.ts b/src/services/CasperServiceByJsonRPC.ts index f2e769e4e..5b3a94224 100644 --- a/src/services/CasperServiceByJsonRPC.ts +++ b/src/services/CasperServiceByJsonRPC.ts @@ -37,9 +37,15 @@ export interface GetStateRootHashResult extends RpcResult { state_root_hash: string; } -interface ExecutionResult { +interface ExecutionResultBody { cost: number; - error_message: string | null; + error_message?: string | null; + transfers: string[]; +} + +export interface ExecutionResult { + Success?: ExecutionResultBody; + Failure?: ExecutionResultBody; } export interface JsonExecutionResult { diff --git a/test/lib/SignedMessage.test.ts b/test/lib/SignedMessage.test.ts index 4f8c5970c..36c004675 100644 --- a/test/lib/SignedMessage.test.ts +++ b/test/lib/SignedMessage.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { Ed25519, Secp256K1 } from '../../src/lib/Keys'; -import { signMessage, verifyMessageSignature } from '../../src/index'; +import { formatMessageWithHeaders, signRawMessage, verifyMessageSignature } from '../../src/index'; describe('SignedMessage', () => { it('Should generate proper signed message and validate it (Ed25519)', () => { @@ -8,7 +8,7 @@ describe('SignedMessage', () => { const exampleMessage = "Hello World!"; const wrongMessage = "!Hello World"; - const signature = signMessage(signKeyPair, exampleMessage); + const signature = signRawMessage(signKeyPair, exampleMessage); const valid = verifyMessageSignature(signKeyPair.publicKey, exampleMessage, signature); const invalid = verifyMessageSignature(signKeyPair.publicKey, wrongMessage, signature); @@ -21,11 +21,19 @@ describe('SignedMessage', () => { const exampleMessage = "Hello World!"; const wrongMessage = "!Hello World"; - const signature = signMessage(signKeyPair, exampleMessage); + const signature = signRawMessage(signKeyPair, exampleMessage); const valid = verifyMessageSignature(signKeyPair.publicKey, exampleMessage, signature); const invalid = verifyMessageSignature(signKeyPair.publicKey, wrongMessage, signature); expect(valid).to.be.eq(true); expect(invalid).to.be.eq(false); }); + + it('Should format message and parse it back correctly', () => { + const message = "Hello World!"; + const messageWithHeaders = formatMessageWithHeaders(message); + expect(messageWithHeaders).to.be.instanceOf(Uint8Array); + const formattedMessageAsString = new TextDecoder().decode(messageWithHeaders); + expect(formattedMessageAsString).to.be.eq('Casper Message:\n' + message, `Messages not equal, recieved: ${formattedMessageAsString}`); + }) }); diff --git a/test/nctl/RPC.test.ts b/test/nctl/RPC.test.ts index e328e725a..999a1901e 100644 --- a/test/nctl/RPC.test.ts +++ b/test/nctl/RPC.test.ts @@ -7,7 +7,7 @@ import { } from '../../src/services'; import { Keys, DeployUtil, RuntimeArgs } from '../../src/index'; -let client = new CasperServiceByJsonRPC('http://127.0.0.1:40101/rpc'); +let client = new CasperServiceByJsonRPC('http://3.139.47.90:7777/rpc'); describe('RPC', () => { xit('should return correct block by number', async () => { @@ -99,14 +99,4 @@ describe('RPC', () => { client.stop(); }, 6 * 10000); }); - - xit('get-dictionary-item using uref', async () => { - const client = new CasperServiceByJsonRPC('http://127.0.0.1:11101/rpc'); - const v = await client.getDictionaryItemByURef( - 'b002db8e962a6abd8f5db2eafb681dbc420183f65464906876238757d99e47de', - '09c308da66ef5306ab1b83dd5b460340f97fd8604f4c526fa2bd2990a531baf9', - 'uref-a0fbf737e6ce3350d1c4eafb2615917bb736389ac771a176c1ddb47c27649839-007' - ); - console.log(v); - }); });