diff --git a/packages/keyring/src/keyring.ts b/packages/keyring/src/keyring.ts index c31338cfa0..84b8d70e10 100644 --- a/packages/keyring/src/keyring.ts +++ b/packages/keyring/src/keyring.ts @@ -4,8 +4,8 @@ import type { EncryptedJsonEncoding, Keypair, KeypairType } from '@polkadot/util-crypto/types'; import type { KeyringInstance, KeyringOptions, KeyringPair, KeyringPair$Json, KeyringPair$Meta } from './types'; -import { assert, hexToU8a, isHex, isUndefined, stringToU8a } from '@polkadot/util'; -import { base64Decode, decodeAddress, ed25519PairFromSeed as ed25519FromSeed, encodeAddress, encrypt as cryptoEncrypt, ethereumEncode, hdEthereum, keyExtractSuri, keyFromPath, mnemonicToLegacySeed, mnemonicToMiniSecret, secp256k1PairFromSeed as secp256k1FromSeed, sr25519PairFromSeed as sr25519FromSeed } from '@polkadot/util-crypto'; +import { assert, hexToU8a, isHex, isUndefined, stringToU8a, u8aConcat } from '@polkadot/util'; +import { base64Decode, decodeAddress, ed25519PairFromSeed as ed25519FromSeed, encodeAddress, encrypt as cryptoEncrypt, ethereumEncode, hdEthereum, keyExtractSuri, keyFromPath, mnemonicToLegacySeed, mnemonicToMiniSecret, secp256k1PairFromSeed as secp256k1FromSeed, sr25519PairFromSeed as sr25519FromSeed, convertPublicKeyToCurve25519, sr25519DerivePublic } from '@polkadot/util-crypto'; import { DEV_PHRASE } from './defaults'; import { createPair } from './pair'; @@ -304,6 +304,33 @@ export class Keyring implements KeyringInstance { * The encrypted message can be decrypted by the corresponding keypair using keypair.decrypt() method */ public encrypt (message: string | Uint8Array, recipientPublicKey: string | Uint8Array, recipientKeyType?: KeypairType): Uint8Array { - return cryptoEncrypt(message, recipientPublicKey, recipientKeyType || this.type); + const publicKey = decodeAddress(recipientPublicKey); + let keyType = recipientKeyType ? recipientKeyType : this.guessKeyType(publicKey); + const encryptedMessage = cryptoEncrypt(message, recipientPublicKey, keyType); + return u8aConcat(publicKey, encryptedMessage); + } + + private guessKeyType (publicKey: Uint8Array): KeypairType { + if(this.isEd25519PublicKey(publicKey)) { + return "ed25519"; + } { + return "sr25519"; + } + } + + private isEd25519PublicKey (publicKey: Uint8Array): boolean { + try { + convertPublicKeyToCurve25519(publicKey); + return true; + } catch(e) { + return false; + } + } + + public decrypt (message: Uint8Array): Uint8Array | null { + const publicKeyBytes = message.slice(0, 32); + const encryptedMessage = message.slice(32); + const keyPair = this.getPair(publicKeyBytes); + return keyPair.decrypt(encryptedMessage); } } diff --git a/packages/keyring/src/pair/index.spec.ts b/packages/keyring/src/pair/index.spec.ts index a0f2bb6828..68425ceea0 100644 --- a/packages/keyring/src/pair/index.spec.ts +++ b/packages/keyring/src/pair/index.spec.ts @@ -243,7 +243,7 @@ describe('pair', (): void => { expect(keyring.alice.decrypt(encryptedMessage)).toEqual(null); }); - it('allows encrypt/decrypt with sr25519 keypair', (): void => { + it('allows encrypt/decrypt with sr25519 keypairs', (): void => { const aliceSR25519KeyPair = new Keyring().createFromUri(mnemonicGenerate(), { name: 'sr25519 pair' }, 'sr25519'); const bobSR25519KeyPair = new Keyring().createFromUri(mnemonicGenerate(), { name: 'sr25519 pair' }, 'sr25519'); const message = new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]); @@ -253,11 +253,40 @@ describe('pair', (): void => { expect((): Uint8Array | null => aliceSR25519KeyPair.decrypt(encryptedMessage)).toThrow("Mac values don't match"); }); + it('allows encrypt/decrypt with ed25519 keypair and type guessing', (): void => { + const keyring = new Keyring(); + const bobED25519KeyPair = keyring.addFromUri(mnemonicGenerate(), { name: 'ed25519 pair' }, 'ed25519'); + const message = new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]); + const encryptedMessage = keyring.encrypt(message, bobED25519KeyPair.publicKey); + + expect(keyring.decrypt(encryptedMessage)).toEqual(message); + }); + + it('allows encrypt/decrypt with sr25519 keypair and type guessing with non-convertible public key', (): void => { + const keyring = new Keyring(); + const mnemonic = "easily dust bright surface scale walnut stick buddy spare quick trial crane"; + const bobSR25519KeyPair = keyring.addFromUri(mnemonic, { name: 'sr25519 pair' }, 'sr25519'); + const message = new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]); + const encryptedMessage = keyring.encrypt(message, bobSR25519KeyPair.publicKey); + + expect(keyring.decrypt(encryptedMessage)).toEqual(message); + }); + + it('allows encrypt/decrypt with sr25519 keypair and type guessing with convertible public key', (): void => { + const keyring = new Keyring(); + const mnemonic = "excess control wrestle sustain gauge swear unveil trap tunnel coil nurse salt"; + const bobSR25519KeyPair = keyring.addFromUri(mnemonic, { name: 'sr25519 pair' }, 'sr25519'); + const message = new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]); + const encryptedMessage = keyring.encrypt(message, bobSR25519KeyPair.publicKey); + + expect(keyring.decrypt(encryptedMessage)).toEqual(message); + }); + it('allows encrypt for an ed25519 keypair', (): void => { const message = new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]); const encryptedMessage = new Keyring().encrypt(message, keyring.alice.publicKey, keyring.alice.type); - expect(keyring.alice.decrypt(encryptedMessage)).toEqual(message); + expect(keyring.alice.decrypt(encryptedMessage.slice(32))).toEqual(message); }); it('allows encrypt for an sr25519 keypair', (): void => { @@ -266,7 +295,7 @@ describe('pair', (): void => { const message = new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]); const encryptedMessage = keyring.encrypt(message, sr25519KeyPair.publicKey, sr25519KeyPair.type); - expect(sr25519KeyPair.decrypt(encryptedMessage)).toEqual(message); + expect(sr25519KeyPair.decrypt(encryptedMessage.slice(32))).toEqual(message); }); it('fails to encrypt when locked', (): void => {