- Allow for hasher to secp256k1 sign/verify functions (#657)

This commit is contained in:
Jaco Greeff
2020-08-11 08:54:52 +02:00
committed by GitHub
parent 99ffcc52ec
commit e487d0a454
14 changed files with 138 additions and 62 deletions
+4
View File
@@ -1,5 +1,9 @@
# CHANGELOG
## 3.2.0-beta.x
- Allow for (optional, default blake2) hasher to secp256k1 sign/verify functions
## 3.1.1 Aug 10, 2020
- Add `deriveAddress(<address>, '/<soft>/<soft-b>')` via sr25519 soft
+2 -2
View File
@@ -22,8 +22,8 @@
},
"devDependencies": {
"@babel/core": "^7.11.1",
"@polkadot/dev": "^0.55.36",
"@polkadot/ts": "^0.3.36",
"@polkadot/dev": "^0.55.37",
"@polkadot/ts": "^0.3.37",
"@types/jest": "^26.0.9"
},
"version": "3.1.1"
+1 -1
View File
@@ -24,6 +24,6 @@ export default function keccakAsU8a (value: Buffer | Uint8Array | string): Uint8
return isReady()
? keccak256(u8aToU8a(value))
: new Uint8Array(
jssha3.keccak256.update(value).arrayBuffer()
jssha3.keccak256.update(u8aToU8a(value)).arrayBuffer()
);
}
@@ -9,5 +9,6 @@ import blake2AsU8a from '../blake2/asU8a';
const HDKD = compactAddLength(stringToU8a('Secp256k1HDKD'));
export default function deriveHard (seed: Uint8Array, chainCode: Uint8Array): Uint8Array {
// NOTE This is specific to the Substrate HDD derivation, so always use the blake2 hasher
return blake2AsU8a(u8aConcat(HDKD, seed, chainCode), 256);
}
@@ -0,0 +1,29 @@
// Copyright 2017-2020 @polkadot/util-crypto authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import hasher from './hasher';
describe('secp256k1Hasher', (): void => {
it('fails with unknown hasher', (): void => {
expect(
() => hasher('unknown' as 'blake2', 'testing')
).toThrow(/Unsupported secp256k1 hasher 'unknown', expected one of blake2, keccak/);
});
it('creates a blake2 hash', (): void => {
expect(
hasher('blake2', 'abc')
).toEqual(
new Uint8Array([189, 221, 129, 60, 99, 66, 57, 114, 49, 113, 239, 63, 238, 152, 87, 155, 148, 150, 78, 59, 177, 203, 62, 66, 114, 98, 200, 192, 104, 213, 35, 25])
);
});
it('creates a keccak hash', (): void => {
expect(
hasher('keccak', 'abc')
).toEqual(
new Uint8Array([78, 3, 101, 122, 234, 69, 169, 79, 199, 212, 123, 168, 38, 200, 214, 103, 192, 209, 230, 227, 58, 100, 160, 54, 236, 68, 245, 143, 161, 45, 108, 69])
);
});
});
@@ -0,0 +1,20 @@
// Copyright 2017-2020 @polkadot/util-crypto authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { HashType } from './types';
import { blake2AsU8a } from '../blake2';
import { keccakAsU8a } from '../keccak';
const HASH_TYPES = ['blake2', 'keccak'];
export default function secp256k1Hasher (hashType: HashType, data: Uint8Array | string): Uint8Array {
if (hashType === 'blake2') {
return blake2AsU8a(data, 256);
} else if (hashType === 'keccak') {
return keccakAsU8a(data);
}
throw new Error(`Unsupported secp256k1 hasher '${hashType as string}', expected one of ${HASH_TYPES.join(', ')}`);
}
@@ -6,7 +6,36 @@ import { u8aToHex, hexToU8a } from '@polkadot/util';
import { mnemonicToMiniSecret } from '../../mnemonic';
import { secp256k1KeypairFromSeed } from '..';
import tests from './testing';
// mnemonic, secret, public, account_id
type Test = [string, string, string, string];
const tests: Test[] = [
[
'life fee table ahead modify maximum dumb such tobacco boss dry nurse',
'0xf2360e871c830d397fe221382b503f07ddd8763df81a94bb2504390a2fb91f59',
'0x036b0aa6beab469dd2b748a0ff5ddbe3d13df1e15c9d28a2aa057212994e127bea',
'0xae8e8fcacbaeb607bcdf0bbd7e615f2b4ef484ee54f19d68a7393fb6db2dd9cd'
],
[
'tide survey cradle cover column ugly author wait eye state elder blame',
'0x5385355a5118ec732b9dbcf1668ba21db38b07cf79082dafa9a7cc4b52e4abb0',
'0x03929e4f93cdad265751ad8f6365185d8e937610d19b510400f5867d542d60a313',
'0xf80ea815da66c42f870b687e1530770d5a7936ae81a147b009506d85bd6d621c'
],
[
'laugh fish flee cake approve butter april dynamic myth license ticket lobster',
'0x83ec65cf9a8a7442d808aef6f8987599f1ba3be880769bb3a20621b13adbd476',
'0x0388299e4cfaa33d180a026bd54a46ad98df129a131320a9d2fd6f80e64bc3db39',
'0x35036238dd195f4c2169379354bda6cba5746f67bde03ef59a77a4cea80729bc'
],
[
'animal thing fork recipe exotic pilot inquiry pledge obey slab obtain reveal',
'0x0fd50580eb5a58b0eee60c77656dffa50094b539262366f1227d3babfd7343e5',
'0x036edc954685ad89f0a23b0fb1eb2b9c3a8600eee9091c758426dfb2bc7889a7c3',
'0x2a94b10d1f28810dc4628e7e424b2d08bd3d17fb08f9416d112f17e86c8fa77c'
]
];
describe('secp256k1KeypairFromSeed', (): void => {
const TEST = hexToU8a('0x4380de832af797688026ce24f85204d508243f201650c1a134929e5458b7fbae');
@@ -3,8 +3,9 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { Keypair } from '../../types';
import { assert, hexToU8a } from '@polkadot/util';
import elliptic from 'elliptic';
import { assert, hexToU8a } from '@polkadot/util';
const EC = elliptic.ec;
const ec = new EC('secp256k1');
@@ -1,35 +0,0 @@
// Copyright 2017-2020 @polkadot/util-crypto authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
// mnemonic, secret, public, account_id
type Test = [string, string, string, string];
const tests: Test[] = [
[
'life fee table ahead modify maximum dumb such tobacco boss dry nurse',
'0xf2360e871c830d397fe221382b503f07ddd8763df81a94bb2504390a2fb91f59',
'0x036b0aa6beab469dd2b748a0ff5ddbe3d13df1e15c9d28a2aa057212994e127bea',
'0xae8e8fcacbaeb607bcdf0bbd7e615f2b4ef484ee54f19d68a7393fb6db2dd9cd'
],
[
'tide survey cradle cover column ugly author wait eye state elder blame',
'0x5385355a5118ec732b9dbcf1668ba21db38b07cf79082dafa9a7cc4b52e4abb0',
'0x03929e4f93cdad265751ad8f6365185d8e937610d19b510400f5867d542d60a313',
'0xf80ea815da66c42f870b687e1530770d5a7936ae81a147b009506d85bd6d621c'
],
[
'laugh fish flee cake approve butter april dynamic myth license ticket lobster',
'0x83ec65cf9a8a7442d808aef6f8987599f1ba3be880769bb3a20621b13adbd476',
'0x0388299e4cfaa33d180a026bd54a46ad98df129a131320a9d2fd6f80e64bc3db39',
'0x35036238dd195f4c2169379354bda6cba5746f67bde03ef59a77a4cea80729bc'
],
[
'animal thing fork recipe exotic pilot inquiry pledge obey slab obtain reveal',
'0x0fd50580eb5a58b0eee60c77656dffa50094b539262366f1227d3babfd7343e5',
'0x036edc954685ad89f0a23b0fb1eb2b9c3a8600eee9091c758426dfb2bc7889a7c3',
'0x2a94b10d1f28810dc4628e7e424b2d08bd3d17fb08f9416d112f17e86c8fa77c'
]
];
export default tests;
+7 -5
View File
@@ -3,9 +3,12 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { Keypair } from '../types';
import { assert, u8aToU8a, u8aConcat } from '@polkadot/util';
import { blake2AsU8a } from '../blake2';
import { HashType } from './types';
import elliptic from 'elliptic';
import { assert, u8aConcat } from '@polkadot/util';
import hasher from './hasher';
const EC = elliptic.ec;
const ec = new EC('secp256k1');
@@ -14,12 +17,11 @@ const ec = new EC('secp256k1');
* @name secp256k1Sign
* @description Returns message signature of `message`, using the supplied pair
*/
export default function secp256k1Sign (message: Uint8Array | string, { secretKey }: Partial<Keypair>): Uint8Array {
export default function secp256k1Sign (message: Uint8Array | string, { secretKey }: Partial<Keypair>, hashType: HashType = 'blake2'): Uint8Array {
assert(secretKey?.length === 32, 'Expected valid secp256k1 secretKey, 32-bytes');
const messageHash = blake2AsU8a(u8aToU8a(message), 256);
const key = ec.keyFromPrivate(secretKey);
const ecsig = key.sign(messageHash);
const ecsig = key.sign(hasher(hashType, message));
const rParam = new Uint8Array(ecsig.r.toArray());
const sParam = new Uint8Array(ecsig.s.toArray());
const recoveryParam = Uint8Array.of(ecsig.recoveryParam || 0);
@@ -3,9 +3,10 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { stringToU8a } from '@polkadot/util';
import { blake2AsU8a } from '../blake2';
import randomAsU8a from '../random/asU8a';
import pairFromSeed from './keypair/fromSeed';
import hasher from './hasher';
import sign from './sign';
import verify from './verify';
@@ -29,8 +30,24 @@ describe('sign and verify', (): void => {
it('can sign and verify a message by random key', (): void => {
const pair = pairFromSeed(randomAsU8a());
const signature = sign(MESSAGE, pair);
const address = blake2AsU8a(pair.publicKey, 256);
const address = hasher('blake2', pair.publicKey);
expect(verify(MESSAGE, signature, address)).toBe(true);
});
it('can sign and verify a message by random key (keccak)', (): void => {
const pair = pairFromSeed(randomAsU8a());
const signature = sign(MESSAGE, pair, 'keccak');
const address = hasher('keccak', pair.publicKey);
expect(verify(MESSAGE, signature, address, 'keccak')).toBe(true);
});
it('can fals verification on hasher mismatches', (): void => {
const pair = pairFromSeed(randomAsU8a());
const signature = sign(MESSAGE, pair, 'keccak');
const address = hasher('keccak', pair.publicKey);
expect(verify(MESSAGE, signature, address, 'blake2')).toBe(false);
});
});
@@ -0,0 +1,5 @@
// Copyright 2017-2020 @polkadot/util-crypto authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
export type HashType = 'blake2' | 'keccak';
+8 -5
View File
@@ -2,9 +2,12 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { u8aToU8a } from '@polkadot/util';
import { blake2AsU8a } from '../blake2';
import { HashType } from './types';
import elliptic from 'elliptic';
import { u8aToU8a } from '@polkadot/util';
import hasher from './hasher';
const EC = elliptic.ec;
const ec = new EC('secp256k1');
@@ -13,7 +16,7 @@ const ec = new EC('secp256k1');
* @name secp256k1Verify
* @description Verifies the signature of `message`, using the supplied pair
*/
export default function secp256k1Verify (message: Uint8Array | string, signature: Uint8Array | string, address: Uint8Array | string): boolean {
export default function secp256k1Verify (message: Uint8Array | string, signature: Uint8Array | string, address: Uint8Array | string, hashType: HashType = 'blake2'): boolean {
const signatureU8a = u8aToU8a(signature);
const sig = {
r: signatureU8a.slice(0, 32),
@@ -22,9 +25,9 @@ export default function secp256k1Verify (message: Uint8Array | string, signature
const recovery = signatureU8a[64];
const publicKey = new Uint8Array(
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
ec.recoverPubKey(blake2AsU8a(message, 256), sig, recovery)
ec.recoverPubKey(hasher(hashType, message), sig, recovery)
.encodeCompressed(null)
);
return Buffer.compare(blake2AsU8a(publicKey, 256), u8aToU8a(address)) === 0;
return Buffer.compare(hasher(hashType, publicKey), u8aToU8a(address)) === 0;
}
+10 -10
View File
@@ -2731,9 +2731,9 @@ __metadata:
languageName: node
linkType: hard
"@polkadot/dev@npm:^0.55.36":
version: 0.55.36
resolution: "@polkadot/dev@npm:0.55.36"
"@polkadot/dev@npm:^0.55.37":
version: 0.55.37
resolution: "@polkadot/dev@npm:0.55.37"
dependencies:
"@babel/cli": ^7.10.5
"@babel/core": ^7.11.1
@@ -2820,7 +2820,7 @@ __metadata:
polkadot-exec-typedoc: scripts/polkadot-exec-typedoc.js
polkadot-exec-vuepress: scripts/polkadot-exec-vuepress.js
polkadot-exec-webpack: scripts/polkadot-exec-webpack.js
checksum: d651ef83ef38cabd7f40e3afe2d696d5bb01d5ce6d7ac6a0f5840e6f3795840412136bf48e8e57035b64acdff67699617523a97ec348b882af930128a056d28f
checksum: f31f1d6f5ad62c7ccd0f889644e8a1e223f853f27e47656d633fb0162a604efa459d3469aa16bffa3a806edc2205158da8223afa35ca97f2af39ec6ba6741ce2
languageName: node
linkType: hard
@@ -2834,12 +2834,12 @@ __metadata:
languageName: unknown
linkType: soft
"@polkadot/ts@npm:^0.3.36":
version: 0.3.36
resolution: "@polkadot/ts@npm:0.3.36"
"@polkadot/ts@npm:^0.3.37":
version: 0.3.37
resolution: "@polkadot/ts@npm:0.3.37"
dependencies:
"@types/chrome": ^0.0.122
checksum: b5afdb242a5a0ab0e94e8b9b0a00c2e52dc765ba4f4ffb4694c3246e38a968920670c695773b9ac60201bcc1585a26dbfedfa4e510b3b4bc3c4a7e9bee7021ae
checksum: 46be0d8e509ea7032ad51933a4e722b7056d5d867bfcafafe5f3449d8008676dd8f4bf147fe5c58e4551f5f1df2a7176fe72c7e8e388809ac487997866218ef8
languageName: node
linkType: hard
@@ -15884,8 +15884,8 @@ __metadata:
resolution: "root-workspace-0b6124@workspace:."
dependencies:
"@babel/core": ^7.11.1
"@polkadot/dev": ^0.55.36
"@polkadot/ts": ^0.3.36
"@polkadot/dev": ^0.55.37
"@polkadot/ts": ^0.3.37
"@types/jest": ^26.0.9
languageName: unknown
linkType: soft