From 72f984a338ba4cc4995cfcf16e55b4c8c7a5f249 Mon Sep 17 00:00:00 2001 From: Bradley Hart Date: Thu, 26 Dec 2019 14:30:21 -0500 Subject: [PATCH] Optional functionality for R1 support Add tests --- src/PrivateKey.ts | 15 ++-- src/PublicKey.ts | 8 +- src/Signature.ts | 30 +++++-- src/eosjs-jssig.ts | 7 +- src/tests/eosjs-jssig.test.ts | 142 ++++++++++++++++++++++++---------- 5 files changed, 140 insertions(+), 62 deletions(-) diff --git a/src/PrivateKey.ts b/src/PrivateKey.ts index 1eb08d28b..3bd1d5fc4 100644 --- a/src/PrivateKey.ts +++ b/src/PrivateKey.ts @@ -12,7 +12,6 @@ export class PrivateKey { /** Instantiate private key from an `elliptic`-format private key */ public static fromElliptic(privKey: ec.KeyPair, keyType = KeyType.k1): PrivateKey { - const privArray = privKey.getPrivate().toArray(); return new PrivateKey({ type: keyType, data: privKey.getPrivate().toBuffer(), @@ -21,19 +20,15 @@ export class PrivateKey { /** Instantiate private key from an EOSIO-format private key */ public static fromString(keyString: string): PrivateKey { - const key: Key = stringToPrivateKey(keyString); - if (key.type !== KeyType.k1) { - throw new Error('Key type isn\'t k1'); - } - return new PrivateKey(key); + return new PrivateKey(stringToPrivateKey(keyString)); } /** Export private key as `elliptic`-format private key */ public toElliptic(ecurve?: ec) { /** expensive to construct; so we do it only as needed */ if (!ecurve) { - if (this.key.type === KeyType.r1) { - ecurve = new ec('secp256r1') as any; + if (this.key.type === KeyType.r1 || this.key.type === KeyType.wa) { + ecurve = new ec('p256') as any; } else { ecurve = new ec('secp256k1') as any; } @@ -45,4 +40,8 @@ export class PrivateKey { public toString(): string { return privateKeyToString(this.key); } + + public getType(): KeyType { + return this.key.type; + } } diff --git a/src/PublicKey.ts b/src/PublicKey.ts index b1607039a..2f294b40b 100644 --- a/src/PublicKey.ts +++ b/src/PublicKey.ts @@ -34,8 +34,8 @@ export class PublicKey { public toElliptic(ecurve?: ec): ec.KeyPair { /** expensive to construct; so we do it only as needed */ if (!ecurve) { - if (this.key.type === KeyType.r1) { - ecurve = new ec('secp256r1') as any; + if (this.key.type === KeyType.r1 || this.key.type === KeyType.wa) { + ecurve = new ec('p256') as any; } else { ecurve = new ec('secp256k1') as any; } @@ -44,4 +44,8 @@ export class PublicKey { pub: new Buffer(this.key.data), }); } + + public getType(): KeyType { + return this.key.type; + } } diff --git a/src/Signature.ts b/src/Signature.ts index df594d100..ce2359986 100644 --- a/src/Signature.ts +++ b/src/Signature.ts @@ -19,16 +19,21 @@ export class Signature { } /** Instantiate Signature from an `elliptic`-format Signature */ - public static fromElliptic(ellipticSig: ec.Signature): Signature { + public static fromElliptic(ellipticSig: ec.Signature, keyType: KeyType = KeyType.k1): Signature { const r = ellipticSig.r.toArray(); const s = ellipticSig.s.toArray(); - let eosioRecoveryParam = ellipticSig.recoveryParam + 27; - if (ellipticSig.recoveryParam <= 3) { - eosioRecoveryParam += 4; + let eosioRecoveryParam; + if (keyType === KeyType.k1) { + eosioRecoveryParam = ellipticSig.recoveryParam + 27; + if (ellipticSig.recoveryParam <= 3) { + eosioRecoveryParam += 4; + } + } else if (keyType === KeyType.r1 || keyType === KeyType.wa) { + eosioRecoveryParam = ellipticSig.recoveryParam; } const sigData = new Uint8Array([eosioRecoveryParam].concat(r, s)); return new Signature({ - type: KeyType.k1, + type: keyType, data: sigData, }); } @@ -45,9 +50,14 @@ export class Signature { const r = new BN(this.signature.data.slice(1, lengthOfR + 1)); const s = new BN(this.signature.data.slice(lengthOfR + 1, lengthOfR + lengthOfS + 1)); - let ellipticRecoveryBitField = this.signature.data[0] - 27; - if (ellipticRecoveryBitField > 3) { - ellipticRecoveryBitField -= 4; + let ellipticRecoveryBitField; + if (this.signature.type === KeyType.k1) { + ellipticRecoveryBitField = this.signature.data[0] - 27; + if (ellipticRecoveryBitField > 3) { + ellipticRecoveryBitField -= 4; + } + } else if (this.signature.type === KeyType.r1 || this.signature.type === KeyType.wa) { + ellipticRecoveryBitField = this.signature.data[0]; } const recoveryParam = ellipticRecoveryBitField & 3; return { r, s, recoveryParam }; @@ -62,4 +72,8 @@ export class Signature { public toBinary(): Uint8Array { return this.signature.data; } + + public getType(): KeyType { + return this.signature.type; + } } diff --git a/src/eosjs-jssig.ts b/src/eosjs-jssig.ts index 51b47a9dc..6fbee87fe 100644 --- a/src/eosjs-jssig.ts +++ b/src/eosjs-jssig.ts @@ -48,9 +48,10 @@ class JsSignatureProvider implements SignatureProvider { /** @param privateKeys private keys to sign with */ constructor(privateKeys: string[]) { for (const k of privateKeys) { - const priv = PrivateKey.fromString(k).toElliptic(defaultEc); - const pubStr = PublicKey.fromElliptic(priv, KeyType.k1).toString(); - this.keys.set(pubStr, priv); + const priv = PrivateKey.fromString(k); + const privElliptic = priv.toElliptic(); + const pubStr = PublicKey.fromElliptic(privElliptic, priv.getType()).toString(); + this.keys.set(pubStr, privElliptic); this.availableKeys.push(pubStr); } } diff --git a/src/tests/eosjs-jssig.test.ts b/src/tests/eosjs-jssig.test.ts index 77639bc7d..049ab4431 100644 --- a/src/tests/eosjs-jssig.test.ts +++ b/src/tests/eosjs-jssig.test.ts @@ -5,6 +5,7 @@ import { JsSignatureProvider, digestFromSerializedData } from '../eosjs-jssig'; +import { KeyType } from '../eosjs-numeric'; import { SignatureProviderArgs } from '../eosjs-api-interfaces'; describe('JsSignatureProvider', () => { @@ -13,6 +14,10 @@ describe('JsSignatureProvider', () => { '5JnHjSFwe4r7xyqAUAaVs51G7HmzE86DWGa3VAA5VvQriGYnSUr', '5K4XZH5XR2By7Q5KTcZnPAmUMU5yjUNBdoKzzXyrLfmiEZJqoKE', ]; + const privateKeysR1 = [ + 'PVT_R1_GrfEfbv5at9kbeHcGagQmvbFLdm6jqEpgE1wsGbrfbZNjpVgT', + 'PVT_R1_wCpPsaY9o8NU9ZsuwaYVQUDkCfj1aWJZGVcmMM6XyYHJVqvqp', + ]; const legacyPublicKeys = [ 'EOS7tgwU6E7pAUQJgqEJt66Yi8cWvanTUW8ZfBjeXeJBQvhTU9ypi', 'EOS8VaY5CiTexYqgQZyPTJkc3qvWuZUi12QrZL9ssjqW2es6aQk2F', @@ -23,6 +28,10 @@ describe('JsSignatureProvider', () => { 'PUB_K1_8VaY5CiTexYqgQZyPTJkc3qvWuZUi12QrZL9ssjqW2es7e7bRJ', 'PUB_K1_7VGhqctkKprW1VUj19DZZiiZLX3YcJqUJCuEcahJmUCw9RT8v2', ]; + const r1FormatPublicKeys = [ + 'PUB_R1_4ztaVy8L9zbmzTdpfq5GcaFYwGwXTNmN3qW7qcgHMmfUZhpzQQ', + 'PUB_R1_5xawnnr3mWayv2wkiqBGWqu4RQLNJffLSXHiL3BofdY7ortMy4', + ]; const signatures = [ 'SIG_K1_HKkqi3zray76i63ZQwAHWMjoLk3wTa1ajZWPcUnrhgmSWQYEHDJsxkny6VDTWEmVdfktxpGoTA81qe6QuCrDmazeQndmxh', 'SIG_K1_HCaY9Y9qdjnkRhE9hokAyp3pFtkMmjpxF6xTd514Vo8vLVSWKek5m5aHfCaka9TqZUbajkhhd4BfBLxSwCwZUEmy8cvt1x', @@ -42,6 +51,12 @@ describe('JsSignatureProvider', () => { expect(actualPublicKeys).toEqual(k1FormatPublicKeys); }); + it('builds p256 elliptic public keys from private when constructed', async () => { + const provider = new JsSignatureProvider(privateKeysR1); + const actualPublicKeys = await provider.getAvailableKeys(); + expect(actualPublicKeys).toEqual(r1FormatPublicKeys); + }); + it('signs a transaction', async () => { const provider = new JsSignatureProvider(privateKeys); const chainId = '12345'; @@ -102,50 +117,95 @@ describe('JsSignatureProvider', () => { ).toEqual(true); }); - it('ensure public key functions are actual inverses of each other', async () => { - const eosioPubKey = PublicKey.fromString(k1FormatPublicKeys[0]); - const ellipticPubKey = eosioPubKey.toElliptic(); - const finalEosioKeyAsK1String = PublicKey.fromElliptic(ellipticPubKey).toString(); - expect(finalEosioKeyAsK1String).toEqual(k1FormatPublicKeys[0]); - }); + describe('secp256k1 elliptic', () => { + it('ensure public key functions are actual inverses of each other', async () => { + const eosioPubKey = PublicKey.fromString(k1FormatPublicKeys[0]); + const ellipticPubKey = eosioPubKey.toElliptic(); + const finalEosioKeyAsK1String = PublicKey.fromElliptic(ellipticPubKey).toString(); + expect(finalEosioKeyAsK1String).toEqual(k1FormatPublicKeys[0]); + }); - it('verify that PUB_K1_ and Legacy pub formats are consistent', () => { - const eosioLegacyPubKey = legacyPublicKeys[0]; - const ellipticPubKey = PublicKey.fromString(eosioLegacyPubKey).toElliptic(); - expect(PublicKey.fromElliptic(ellipticPubKey).toString()).toEqual(k1FormatPublicKeys[0]); - }); + it('verify that PUB_K1_ and Legacy pub formats are consistent', () => { + const eosioLegacyPubKey = legacyPublicKeys[0]; + const ellipticPubKey = PublicKey.fromString(eosioLegacyPubKey).toElliptic(); + expect(PublicKey.fromElliptic(ellipticPubKey).toString()).toEqual(k1FormatPublicKeys[0]); + }); + + it('ensure private key functions are actual inverses of each other', async () => { + const priv = privateKeys[0]; + const privEosioKey = PrivateKey.fromString(priv); + const privEllipticKey = privEosioKey.toElliptic(); + const finalEosioKeyAsString = PrivateKey.fromElliptic(privEllipticKey).toString(); + expect(privEosioKey.toString()).toEqual(finalEosioKeyAsString); + }); - it('ensure private key functions are actual inverses of each other', async () => { - const priv = privateKeys[0]; - const privEosioKey = PrivateKey.fromString(priv); - const privEllipticKey = privEosioKey.toElliptic(); - const finalEosioKeyAsString = PrivateKey.fromElliptic(privEllipticKey).toString(); - expect(privEosioKey.toString()).toEqual(finalEosioKeyAsString); + it('Ensure elliptic sign, recover, verify flow works', () => { + const ellipticEc = new ec('secp256k1'); + const KPriv = privateKeys[0]; + const KPrivElliptic = PrivateKey.fromString(KPriv).toElliptic(); + + const dataAsString = 'some string'; + const ellipticHashedString = ellipticEc.hash().update(dataAsString).digest(); + // const ellipticHashedString = Buffer.from(hashedData); + + const ellipticSig = KPrivElliptic.sign(ellipticHashedString); + // expect(Signature.fromElliptic(ellipticSig).toString()).toEqual(signatures[0]); + const ellipticRecoveredKPub = ellipticEc.recoverPubKey( + ellipticHashedString, + ellipticSig, + ellipticSig.recoveryParam + ); + const ellipticKPub = ellipticEc.keyFromPublic(ellipticRecoveredKPub); + expect(PublicKey.fromElliptic(ellipticKPub).toString()).toEqual(k1FormatPublicKeys[0]); + const ellipticValid = ellipticEc.verify( + ellipticHashedString, + ellipticSig, + ellipticEc.keyFromPublic(ellipticKPub) + ); + expect(ellipticValid).toEqual(true); + }); }); - it('Ensure elliptic sign, recover, verify flow works', () => { - const ellipticEc = new ec('secp256k1'); - const KPriv = privateKeys[0]; - const KPrivElliptic = PrivateKey.fromString(KPriv).toElliptic(); - - const dataAsString = 'some string'; - const ellipticHashedString = ellipticEc.hash().update(dataAsString).digest(); - // const ellipticHashedString = Buffer.from(hashedData); - - const ellipticSig = KPrivElliptic.sign(ellipticHashedString); - // expect(Signature.fromElliptic(ellipticSig).toString()).toEqual(signatures[0]); - const ellipticRecoveredKPub = ellipticEc.recoverPubKey( - ellipticHashedString, - ellipticSig, - ellipticSig.recoveryParam - ); - const ellipticKPub = ellipticEc.keyFromPublic(ellipticRecoveredKPub); - expect(PublicKey.fromElliptic(ellipticKPub).toString()).toEqual(k1FormatPublicKeys[0]); - const ellipticValid = ellipticEc.verify( - ellipticHashedString, - ellipticSig, - ellipticEc.keyFromPublic(ellipticKPub) - ); - expect(ellipticValid).toEqual(true); + describe('p256 elliptic', () => { + it('ensure public key functions using p256 format are actual inverses of each other', async () => { + const eosioPubKey = PublicKey.fromString(r1FormatPublicKeys[0]); + const ellipticPubKey = eosioPubKey.toElliptic(); + const finalEosioKeyAsR1String = PublicKey.fromElliptic(ellipticPubKey, KeyType.r1).toString(); + expect(finalEosioKeyAsR1String).toEqual(r1FormatPublicKeys[0]); + }); + + it('ensure private key functions using p256 format are actual inverses of each other', async () => { + const priv = privateKeysR1[0]; + const privEosioKey = PrivateKey.fromString(priv); + const privEllipticKey = privEosioKey.toElliptic(); + const finalEosioKeyAsString = PrivateKey.fromElliptic(privEllipticKey, KeyType.r1).toString(); + expect(privEosioKey.toString()).toEqual(finalEosioKeyAsString); + }); + + it('Ensure elliptic sign, recover, verify flow works', () => { + const ellipticEc = new ec('p256'); + const KPriv = privateKeysR1[0]; + const KPrivElliptic = PrivateKey.fromString(KPriv).toElliptic(); + + const dataAsString = 'some string'; + const ellipticHashedString = ellipticEc.hash().update(dataAsString).digest(); + // const ellipticHashedString = Buffer.from(hashedData); + + const ellipticSig = KPrivElliptic.sign(ellipticHashedString); + // expect(Signature.fromElliptic(ellipticSig).toString()).toEqual(signatures[0]); + const ellipticRecoveredKPub = ellipticEc.recoverPubKey( + ellipticHashedString, + ellipticSig, + ellipticSig.recoveryParam + ); + const ellipticKPub = ellipticEc.keyFromPublic(ellipticRecoveredKPub); + expect(PublicKey.fromElliptic(ellipticKPub, KeyType.r1).toString()).toEqual(r1FormatPublicKeys[0]); + const ellipticValid = ellipticEc.verify( + ellipticHashedString, + ellipticSig, + ellipticEc.keyFromPublic(ellipticKPub) + ); + expect(ellipticValid).toEqual(true); + }); }); });