EP: Add LibSignature
library (#21)
* `@0x/contracts-zero-ex`: Add `LibSignature` library * `@0x/contracts-zero-ex`: Update package.json scripts Co-authored-by: Lawrence Forman <me@merklejerk.com>
This commit is contained in:
parent
e2e14a977a
commit
f4709ed1cb
@ -5,6 +5,10 @@
|
||||
{
|
||||
"note": "Add support for collecting protocol fees in ETH or WETH",
|
||||
"pr": 2
|
||||
},
|
||||
{
|
||||
"note": "Add `LibSignature` library",
|
||||
"pr": 21
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -26,7 +26,8 @@ library LibSignatureRichErrors {
|
||||
INVALID_LENGTH,
|
||||
UNSUPPORTED,
|
||||
ILLEGAL,
|
||||
WRONG_SIGNER
|
||||
WRONG_SIGNER,
|
||||
BAD_SIGNATURE_DATA
|
||||
}
|
||||
|
||||
// solhint-disable func-name-mixedcase
|
||||
@ -49,4 +50,19 @@ library LibSignatureRichErrors {
|
||||
signature
|
||||
);
|
||||
}
|
||||
|
||||
function SignatureValidationError(
|
||||
SignatureValidationErrorCodes code,
|
||||
bytes32 hash
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
bytes4(keccak256("SignatureValidationError(uint8,bytes32)")),
|
||||
code,
|
||||
hash
|
||||
);
|
||||
}
|
||||
}
|
||||
|
151
contracts/zero-ex/contracts/src/features/libs/LibSignature.sol
Normal file
151
contracts/zero-ex/contracts/src/features/libs/LibSignature.sol
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||
import "../../errors/LibSignatureRichErrors.sol";
|
||||
|
||||
|
||||
/// @dev A library for validating signatures.
|
||||
library LibSignature {
|
||||
using LibRichErrorsV06 for bytes;
|
||||
|
||||
// '\x19Ethereum Signed Message:\n32\x00\x00\x00\x00' in a word.
|
||||
uint256 private constant ETH_SIGN_HASH_PREFIX =
|
||||
0x19457468657265756d205369676e6564204d6573736167653a0a333200000000;
|
||||
/// @dev Exclusive upper limit on ECDSA signatures 'R' values.
|
||||
/// The valid range is given by fig (282) of the yellow paper.
|
||||
uint256 private constant ECDSA_SIGNATURE_R_LIMIT =
|
||||
uint256(0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141);
|
||||
/// @dev Exclusive upper limit on ECDSA signatures 'S' values.
|
||||
/// The valid range is given by fig (283) of the yellow paper.
|
||||
uint256 private constant ECDSA_SIGNATURE_S_LIMIT = ECDSA_SIGNATURE_R_LIMIT / 2 + 1;
|
||||
|
||||
/// @dev Allowed signature types.
|
||||
enum SignatureType {
|
||||
ILLEGAL,
|
||||
INVALID,
|
||||
EIP712,
|
||||
ETHSIGN
|
||||
}
|
||||
|
||||
/// @dev Encoded EC signature.
|
||||
struct Signature {
|
||||
// How to validate the signature.
|
||||
SignatureType signatureType;
|
||||
// EC Signature data.
|
||||
uint8 v;
|
||||
// EC Signature data.
|
||||
bytes32 r;
|
||||
// EC Signature data.
|
||||
bytes32 s;
|
||||
}
|
||||
|
||||
/// @dev Retrieve the signer of a signature.
|
||||
/// Throws if the signature can't be validated.
|
||||
/// @param hash The hash that was signed.
|
||||
/// @param signature The signature.
|
||||
/// @return recovered The recovered signer address.
|
||||
function getSignerOfHash(
|
||||
bytes32 hash,
|
||||
Signature memory signature
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (address recovered)
|
||||
{
|
||||
// Ensure this is a signature type that can be validated against a hash.
|
||||
_validateHashCompatibleSignature(hash, signature);
|
||||
|
||||
if (signature.signatureType == SignatureType.EIP712) {
|
||||
// Signed using EIP712
|
||||
recovered = ecrecover(
|
||||
hash,
|
||||
signature.v,
|
||||
signature.r,
|
||||
signature.s
|
||||
);
|
||||
} else if (signature.signatureType == SignatureType.ETHSIGN) {
|
||||
// Signed using `eth_sign`
|
||||
// Need to hash `hash` with "\x19Ethereum Signed Message:\n32" prefix
|
||||
// in packed encoding.
|
||||
bytes32 ethSignHash;
|
||||
assembly {
|
||||
// Use scratch space
|
||||
mstore(0, ETH_SIGN_HASH_PREFIX) // length of 28 bytes
|
||||
mstore(28, hash) // length of 32 bytes
|
||||
ethSignHash := keccak256(0, 60)
|
||||
}
|
||||
recovered = ecrecover(
|
||||
ethSignHash,
|
||||
signature.v,
|
||||
signature.r,
|
||||
signature.s
|
||||
);
|
||||
}
|
||||
// `recovered` can be null if the signature values are out of range.
|
||||
if (recovered == address(0)) {
|
||||
LibSignatureRichErrors.SignatureValidationError(
|
||||
LibSignatureRichErrors.SignatureValidationErrorCodes.BAD_SIGNATURE_DATA,
|
||||
hash
|
||||
).rrevert();
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Validates that a signature is compatible with a hash signee.
|
||||
/// @param hash The hash that was signed.
|
||||
/// @param signature The signature.
|
||||
function _validateHashCompatibleSignature(
|
||||
bytes32 hash,
|
||||
Signature memory signature
|
||||
)
|
||||
private
|
||||
pure
|
||||
{
|
||||
// Ensure the r and s are within malleability limits.
|
||||
if (uint256(signature.r) >= ECDSA_SIGNATURE_R_LIMIT ||
|
||||
uint256(signature.s) >= ECDSA_SIGNATURE_S_LIMIT)
|
||||
{
|
||||
LibSignatureRichErrors.SignatureValidationError(
|
||||
LibSignatureRichErrors.SignatureValidationErrorCodes.BAD_SIGNATURE_DATA,
|
||||
hash
|
||||
).rrevert();
|
||||
}
|
||||
|
||||
// Always illegal signature.
|
||||
if (signature.signatureType == SignatureType.ILLEGAL) {
|
||||
LibSignatureRichErrors.SignatureValidationError(
|
||||
LibSignatureRichErrors.SignatureValidationErrorCodes.ILLEGAL,
|
||||
hash
|
||||
).rrevert();
|
||||
}
|
||||
|
||||
// Always invalid.
|
||||
if (signature.signatureType == SignatureType.INVALID) {
|
||||
LibSignatureRichErrors.SignatureValidationError(
|
||||
LibSignatureRichErrors.SignatureValidationErrorCodes.ALWAYS_INVALID,
|
||||
hash
|
||||
).rrevert();
|
||||
}
|
||||
|
||||
// Solidity should check that the signature type is within enum range for us
|
||||
// when abi-decoding.
|
||||
}
|
||||
}
|
34
contracts/zero-ex/contracts/test/TestLibSignature.sol
Normal file
34
contracts/zero-ex/contracts/test/TestLibSignature.sol
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "../src/features/libs/LibSignature.sol";
|
||||
|
||||
|
||||
contract TestLibSignature {
|
||||
|
||||
function getSignerOfHash(bytes32 hash, LibSignature.Signature calldata signature)
|
||||
external
|
||||
pure
|
||||
returns (address signer)
|
||||
{
|
||||
return LibSignature.getSignerOfHash(hash, signature);
|
||||
}
|
||||
}
|
@ -10,8 +10,9 @@
|
||||
"test": "test"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "yarn pre_build && tsc -b",
|
||||
"build": "yarn pre_build && yarn build:ts",
|
||||
"build:ci": "yarn build",
|
||||
"build:ts": "tsc -b",
|
||||
"pre_build": "run-s compile contracts:gen generate_contract_wrappers contracts:copy",
|
||||
"test": "yarn run_mocha",
|
||||
"rebuild_and_test": "run-s build test",
|
||||
@ -41,7 +42,7 @@
|
||||
"config": {
|
||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,SignatureValidatorFeature,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|FeeCollector|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|ILiquidityProviderFeature|IMetaTransactionsFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibLiquidityProviderRichErrors|LibLiquidityProviderStorage|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpender|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LogMetadataTransformer|MetaTransactionsFeature|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinDodo|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|OwnableFeature|PayTakerTransformer|SignatureValidatorFeature|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestLibTokenSpender|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestProtocolFees|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx).json"
|
||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|FeeCollector|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|ILiquidityProviderFeature|IMetaTransactionsFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibLiquidityProviderRichErrors|LibLiquidityProviderStorage|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpender|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LogMetadataTransformer|MetaTransactionsFeature|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinDodo|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|OwnableFeature|PayTakerTransformer|SignatureValidatorFeature|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestLibSignature|TestLibTokenSpender|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestProtocolFees|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -76,6 +77,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^6.2.11",
|
||||
"@0x/order-utils": "^10.4.2",
|
||||
"@0x/subproviders": "^6.1.9",
|
||||
"@0x/types": "^3.3.0",
|
||||
"@0x/typescript-typings": "^5.1.5",
|
||||
|
@ -32,6 +32,7 @@ export { artifacts } from './artifacts';
|
||||
export * from './migration';
|
||||
export * from './nonce_utils';
|
||||
export * from './signed_call_data';
|
||||
export * from './signature_utils';
|
||||
export {
|
||||
AffiliateFeeTransformerContract,
|
||||
BridgeAdapterContract,
|
||||
@ -48,3 +49,6 @@ export {
|
||||
WethTransformerContract,
|
||||
ZeroExContract,
|
||||
} from './wrappers';
|
||||
export * from './revert_errors';
|
||||
export { EIP712TypedData } from '@0x/types';
|
||||
export { SupportedProvider } from '@0x/subproviders';
|
||||
|
30
contracts/zero-ex/src/revert_errors.ts
Normal file
30
contracts/zero-ex/src/revert_errors.ts
Normal file
@ -0,0 +1,30 @@
|
||||
// TODO(dorothy-zbornak): Move these into `@0x/protocol-utils` whenever that
|
||||
// becomes a thing.
|
||||
// tslint:disable:max-classes-per-file
|
||||
import { RevertError } from '@0x/utils';
|
||||
|
||||
export enum SignatureValidationErrorCodes {
|
||||
AlwaysInvalid = 0,
|
||||
InvalidLength = 1,
|
||||
Unsupported = 2,
|
||||
Illegal = 3,
|
||||
WrongSigner = 4,
|
||||
BadSignatureData = 5,
|
||||
}
|
||||
|
||||
// tslint:disable:max-classes-per-file
|
||||
export class SignatureValidationError extends RevertError {
|
||||
constructor(code?: SignatureValidationErrorCodes, hash?: string) {
|
||||
super('SignatureValidationError', 'SignatureValidationError(uint8 code, bytes32 hash)', {
|
||||
code,
|
||||
hash,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const types = [SignatureValidationError];
|
||||
|
||||
// Register the types we've defined.
|
||||
for (const type of types) {
|
||||
RevertError.registerType(type);
|
||||
}
|
113
contracts/zero-ex/src/signature_utils.ts
Normal file
113
contracts/zero-ex/src/signature_utils.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import { signatureUtils } from '@0x/order-utils';
|
||||
import { SupportedProvider } from '@0x/subproviders';
|
||||
import { EIP712TypedData } from '@0x/types';
|
||||
import { hexUtils, signTypedDataUtils } from '@0x/utils';
|
||||
import * as ethjs from 'ethereumjs-util';
|
||||
|
||||
/**
|
||||
* Valid signature types on the Exchange Proxy.
|
||||
*/
|
||||
export enum SignatureType {
|
||||
Illegal = 0,
|
||||
Invalid = 1,
|
||||
EIP712 = 2,
|
||||
EthSign = 3,
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a raw EC signature.
|
||||
*/
|
||||
export interface ECSignature {
|
||||
v: number;
|
||||
r: string;
|
||||
s: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A complete signature on the Exchange Proxy.
|
||||
*/
|
||||
export interface Signature extends ECSignature {
|
||||
signatureType: SignatureType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a hash with the EthSign signature type on a provider.
|
||||
*/
|
||||
export async function ethSignHashFromProviderAsync(
|
||||
signer: string,
|
||||
hash: string,
|
||||
provider: SupportedProvider,
|
||||
): Promise<Signature> {
|
||||
const signatureBytes = await signatureUtils.ecSignHashAsync(provider, hash, signer);
|
||||
const parsed = parsePackedSignatureBytes(signatureBytes);
|
||||
assertSignatureType(parsed, SignatureType.EthSign);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a hash with the EthSign signature type, given a private key.
|
||||
*/
|
||||
export function ethSignHashWithKey(hash: string, key: string): Signature {
|
||||
const ethHash = hexUtils.toHex(
|
||||
ethjs.sha3(hexUtils.concat(ethjs.toBuffer('\x19Ethereum Signed Message:\n32'), hash)),
|
||||
);
|
||||
return {
|
||||
...ecSignHashWithKey(ethHash, key),
|
||||
signatureType: SignatureType.EthSign,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a typed data object with the EIP712 signature type, given a private key.
|
||||
*/
|
||||
export function eip712SignTypedDataWithKey(typedData: EIP712TypedData, key: string): Signature {
|
||||
const hash = hexUtils.toHex(signTypedDataUtils.generateTypedDataHash(typedData));
|
||||
return {
|
||||
...ecSignHashWithKey(hash, key),
|
||||
signatureType: SignatureType.EIP712,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign an EIP712 hash with the EIP712 signature type, given a private key.
|
||||
*/
|
||||
export function eip712SignHashWithKey(hash: string, key: string): Signature {
|
||||
return {
|
||||
...ecSignHashWithKey(hash, key),
|
||||
signatureType: SignatureType.EIP712,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the EC signature for a hash given a private key.
|
||||
*/
|
||||
export function ecSignHashWithKey(hash: string, key: string): ECSignature {
|
||||
const { v, r, s } = ethjs.ecsign(ethjs.toBuffer(hash), ethjs.toBuffer(key));
|
||||
return {
|
||||
v,
|
||||
r: ethjs.bufferToHex(r),
|
||||
s: ethjs.bufferToHex(s),
|
||||
};
|
||||
}
|
||||
|
||||
function assertSignatureType(signature: Signature, expectedType: SignatureType): void {
|
||||
if (signature.signatureType !== expectedType) {
|
||||
throw new Error(`Expected signature type to be ${expectedType} but received ${signature.signatureType}.`);
|
||||
}
|
||||
}
|
||||
|
||||
function parsePackedSignatureBytes(signatureBytes: string): Signature {
|
||||
if (hexUtils.size(signatureBytes) !== 66) {
|
||||
throw new Error(`Expected packed signatureBytes to be 66 bytes long: ${signatureBytes}`);
|
||||
}
|
||||
const typeId = parseInt(signatureBytes.slice(-2), 16) as SignatureType;
|
||||
if (!Object.values(SignatureType).includes(typeId)) {
|
||||
throw new Error(`Invalid signatureBytes type ID detected: ${typeId}`);
|
||||
}
|
||||
return {
|
||||
signatureType: typeId,
|
||||
v: parseInt(signatureBytes.slice(2, 4), 16),
|
||||
r: hexUtils.slice(signatureBytes, 1, 33),
|
||||
s: hexUtils.slice(signatureBytes, 33),
|
||||
};
|
||||
}
|
@ -51,6 +51,7 @@ import * as LibOwnableStorage from '../test/generated-artifacts/LibOwnableStorag
|
||||
import * as LibProxyRichErrors from '../test/generated-artifacts/LibProxyRichErrors.json';
|
||||
import * as LibProxyStorage from '../test/generated-artifacts/LibProxyStorage.json';
|
||||
import * as LibReentrancyGuardStorage from '../test/generated-artifacts/LibReentrancyGuardStorage.json';
|
||||
import * as LibSignature from '../test/generated-artifacts/LibSignature.json';
|
||||
import * as LibSignatureRichErrors from '../test/generated-artifacts/LibSignatureRichErrors.json';
|
||||
import * as LibSignedCallData from '../test/generated-artifacts/LibSignedCallData.json';
|
||||
import * as LibSimpleFunctionRegistryRichErrors from '../test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json';
|
||||
@ -90,6 +91,7 @@ import * as TestFillQuoteTransformerExchange from '../test/generated-artifacts/T
|
||||
import * as TestFillQuoteTransformerHost from '../test/generated-artifacts/TestFillQuoteTransformerHost.json';
|
||||
import * as TestFullMigration from '../test/generated-artifacts/TestFullMigration.json';
|
||||
import * as TestInitialMigration from '../test/generated-artifacts/TestInitialMigration.json';
|
||||
import * as TestLibSignature from '../test/generated-artifacts/TestLibSignature.json';
|
||||
import * as TestLibTokenSpender from '../test/generated-artifacts/TestLibTokenSpender.json';
|
||||
import * as TestMetaTransactionsTransformERC20Feature from '../test/generated-artifacts/TestMetaTransactionsTransformERC20Feature.json';
|
||||
import * as TestMigrator from '../test/generated-artifacts/TestMigrator.json';
|
||||
@ -153,6 +155,7 @@ export const artifacts = {
|
||||
TokenSpenderFeature: TokenSpenderFeature as ContractArtifact,
|
||||
TransformERC20Feature: TransformERC20Feature as ContractArtifact,
|
||||
UniswapFeature: UniswapFeature as ContractArtifact,
|
||||
LibSignature: LibSignature as ContractArtifact,
|
||||
LibSignedCallData: LibSignedCallData as ContractArtifact,
|
||||
LibTokenSpender: LibTokenSpender as ContractArtifact,
|
||||
FixinCommon: FixinCommon as ContractArtifact,
|
||||
@ -208,6 +211,7 @@ export const artifacts = {
|
||||
TestFillQuoteTransformerHost: TestFillQuoteTransformerHost as ContractArtifact,
|
||||
TestFullMigration: TestFullMigration as ContractArtifact,
|
||||
TestInitialMigration: TestInitialMigration as ContractArtifact,
|
||||
TestLibSignature: TestLibSignature as ContractArtifact,
|
||||
TestLibTokenSpender: TestLibTokenSpender as ContractArtifact,
|
||||
TestMetaTransactionsTransformERC20Feature: TestMetaTransactionsTransformERC20Feature as ContractArtifact,
|
||||
TestMigrator: TestMigrator as ContractArtifact,
|
||||
|
98
contracts/zero-ex/test/lib_signature_test.ts
Normal file
98
contracts/zero-ex/test/lib_signature_test.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { blockchainTests, expect } from '@0x/contracts-test-utils';
|
||||
import { hexUtils } from '@0x/utils';
|
||||
import * as ethjs from 'ethereumjs-util';
|
||||
|
||||
import { SignatureValidationError, SignatureValidationErrorCodes } from '../src/revert_errors';
|
||||
import { eip712SignHashWithKey, ethSignHashWithKey, SignatureType } from '../src/signature_utils';
|
||||
|
||||
import { artifacts } from './artifacts';
|
||||
import { TestLibSignatureContract } from './wrappers';
|
||||
|
||||
const EMPTY_REVERT = 'reverted with no data';
|
||||
|
||||
blockchainTests.resets('LibSignature library', env => {
|
||||
let testLib: TestLibSignatureContract;
|
||||
let signerKey: string;
|
||||
let signer: string;
|
||||
|
||||
before(async () => {
|
||||
signerKey = hexUtils.random();
|
||||
signer = ethjs.bufferToHex(ethjs.privateToAddress(ethjs.toBuffer(signerKey)));
|
||||
testLib = await TestLibSignatureContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestLibSignature,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
});
|
||||
|
||||
describe('getSignerOfHash()', () => {
|
||||
it('can recover the signer of an EIP712 signature', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const sig = eip712SignHashWithKey(hash, signerKey);
|
||||
const recovered = await testLib.getSignerOfHash(hash, sig).callAsync();
|
||||
expect(recovered).to.eq(signer);
|
||||
});
|
||||
|
||||
it('can recover the signer of an EthSign signature', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const sig = ethSignHashWithKey(hash, signerKey);
|
||||
const recovered = await testLib.getSignerOfHash(hash, sig).callAsync();
|
||||
expect(recovered).to.eq(signer);
|
||||
});
|
||||
|
||||
it('throws if the signature type is out of range', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const badType = (Object.values(SignatureType).slice(-1)[0] as number) + 1;
|
||||
const sig = {
|
||||
...ethSignHashWithKey(hash, signerKey),
|
||||
signatureType: badType,
|
||||
};
|
||||
return expect(testLib.getSignerOfHash(hash, sig).callAsync()).to.be.rejectedWith(EMPTY_REVERT);
|
||||
});
|
||||
|
||||
it('throws if the signature data is malformed', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const sig = {
|
||||
...ethSignHashWithKey(hash, signerKey),
|
||||
v: 1,
|
||||
};
|
||||
return expect(testLib.getSignerOfHash(hash, sig).callAsync()).to.revertWith(
|
||||
new SignatureValidationError(SignatureValidationErrorCodes.BadSignatureData, hash),
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if an EC value is out of range', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const sig = {
|
||||
...ethSignHashWithKey(hash, signerKey),
|
||||
r: '0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141',
|
||||
};
|
||||
return expect(testLib.getSignerOfHash(hash, sig).callAsync()).to.revertWith(
|
||||
new SignatureValidationError(SignatureValidationErrorCodes.BadSignatureData, hash),
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if the type is Illegal', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const sig = {
|
||||
...ethSignHashWithKey(hash, signerKey),
|
||||
signatureType: SignatureType.Illegal,
|
||||
};
|
||||
return expect(testLib.getSignerOfHash(hash, sig).callAsync()).to.revertWith(
|
||||
new SignatureValidationError(SignatureValidationErrorCodes.Illegal, hash),
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if the type is Invalid', async () => {
|
||||
const hash = hexUtils.random();
|
||||
const sig = {
|
||||
...ethSignHashWithKey(hash, signerKey),
|
||||
signatureType: SignatureType.Invalid,
|
||||
};
|
||||
return expect(testLib.getSignerOfHash(hash, sig).callAsync()).to.revertWith(
|
||||
new SignatureValidationError(SignatureValidationErrorCodes.AlwaysInvalid, hash),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -49,6 +49,7 @@ export * from '../test/generated-wrappers/lib_ownable_storage';
|
||||
export * from '../test/generated-wrappers/lib_proxy_rich_errors';
|
||||
export * from '../test/generated-wrappers/lib_proxy_storage';
|
||||
export * from '../test/generated-wrappers/lib_reentrancy_guard_storage';
|
||||
export * from '../test/generated-wrappers/lib_signature';
|
||||
export * from '../test/generated-wrappers/lib_signature_rich_errors';
|
||||
export * from '../test/generated-wrappers/lib_signed_call_data';
|
||||
export * from '../test/generated-wrappers/lib_simple_function_registry_rich_errors';
|
||||
@ -88,6 +89,7 @@ export * from '../test/generated-wrappers/test_fill_quote_transformer_exchange';
|
||||
export * from '../test/generated-wrappers/test_fill_quote_transformer_host';
|
||||
export * from '../test/generated-wrappers/test_full_migration';
|
||||
export * from '../test/generated-wrappers/test_initial_migration';
|
||||
export * from '../test/generated-wrappers/test_lib_signature';
|
||||
export * from '../test/generated-wrappers/test_lib_token_spender';
|
||||
export * from '../test/generated-wrappers/test_meta_transactions_transform_erc20_feature';
|
||||
export * from '../test/generated-wrappers/test_migrator';
|
||||
|
@ -73,6 +73,7 @@
|
||||
"test/generated-artifacts/LibProxyRichErrors.json",
|
||||
"test/generated-artifacts/LibProxyStorage.json",
|
||||
"test/generated-artifacts/LibReentrancyGuardStorage.json",
|
||||
"test/generated-artifacts/LibSignature.json",
|
||||
"test/generated-artifacts/LibSignatureRichErrors.json",
|
||||
"test/generated-artifacts/LibSignedCallData.json",
|
||||
"test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json",
|
||||
@ -112,6 +113,7 @@
|
||||
"test/generated-artifacts/TestFillQuoteTransformerHost.json",
|
||||
"test/generated-artifacts/TestFullMigration.json",
|
||||
"test/generated-artifacts/TestInitialMigration.json",
|
||||
"test/generated-artifacts/TestLibSignature.json",
|
||||
"test/generated-artifacts/TestLibTokenSpender.json",
|
||||
"test/generated-artifacts/TestMetaTransactionsTransformERC20Feature.json",
|
||||
"test/generated-artifacts/TestMigrator.json",
|
||||
|
Loading…
x
Reference in New Issue
Block a user