index.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. "use strict";
  2. import { Logger } from "@ethersproject/logger";
  3. import { version } from "./_version";
  4. const logger = new Logger(version);
  5. ///////////////////////////////
  6. // Exported Types
  7. export type Bytes = ArrayLike<number>;
  8. export type BytesLike = Bytes | string;
  9. export type DataOptions = {
  10. allowMissingPrefix?: boolean;
  11. hexPad?: "left" | "right" | null;
  12. };
  13. export interface Hexable {
  14. toHexString(): string;
  15. }
  16. /*
  17. export interface HexString {
  18. length: number;
  19. substring: (start: number, end?: number) => string;
  20. [index: number]: string;
  21. }
  22. */
  23. export type SignatureLike = {
  24. r: string;
  25. s?: string;
  26. _vs?: string,
  27. recoveryParam?: number;
  28. v?: number;
  29. } | BytesLike;
  30. export interface Signature {
  31. r: string;
  32. s: string;
  33. _vs: string,
  34. recoveryParam: number;
  35. v: number;
  36. yParityAndS: string
  37. compact: string;
  38. }
  39. ///////////////////////////////
  40. function isHexable(value: any): value is Hexable {
  41. return !!(value.toHexString);
  42. }
  43. function addSlice(array: Uint8Array): Uint8Array {
  44. if (array.slice) { return array; }
  45. array.slice = function() {
  46. const args = Array.prototype.slice.call(arguments);
  47. return addSlice(new Uint8Array(Array.prototype.slice.apply(array, args)));
  48. }
  49. return array;
  50. }
  51. export function isBytesLike(value: any): value is BytesLike {
  52. return ((isHexString(value) && !(value.length % 2)) || isBytes(value));
  53. }
  54. function isInteger(value: number) {
  55. return (typeof(value) === "number" && value == value && (value % 1) === 0);
  56. }
  57. export function isBytes(value: any): value is Bytes {
  58. if (value == null) { return false; }
  59. if (value.constructor === Uint8Array) { return true; }
  60. if (typeof(value) === "string") { return false; }
  61. if (!isInteger(value.length) || value.length < 0) { return false; }
  62. for (let i = 0; i < value.length; i++) {
  63. const v = value[i];
  64. if (!isInteger(v) || v < 0 || v >= 256) { return false; }
  65. }
  66. return true;
  67. }
  68. export function arrayify(value: BytesLike | Hexable | number, options?: DataOptions): Uint8Array {
  69. if (!options) { options = { }; }
  70. if (typeof(value) === "number") {
  71. logger.checkSafeUint53(value, "invalid arrayify value");
  72. const result = [];
  73. while (value) {
  74. result.unshift(value & 0xff);
  75. value = parseInt(String(value / 256));
  76. }
  77. if (result.length === 0) { result.push(0); }
  78. return addSlice(new Uint8Array(result));
  79. }
  80. if (options.allowMissingPrefix && typeof(value) === "string" && value.substring(0, 2) !== "0x") {
  81. value = "0x" + value;
  82. }
  83. if (isHexable(value)) { value = value.toHexString(); }
  84. if (isHexString(value)) {
  85. let hex = (<string>value).substring(2);
  86. if (hex.length % 2) {
  87. if (options.hexPad === "left") {
  88. hex = "0x0" + hex.substring(2);
  89. } else if (options.hexPad === "right") {
  90. hex += "0";
  91. } else {
  92. logger.throwArgumentError("hex data is odd-length", "value", value);
  93. }
  94. }
  95. const result = [];
  96. for (let i = 0; i < hex.length; i += 2) {
  97. result.push(parseInt(hex.substring(i, i + 2), 16));
  98. }
  99. return addSlice(new Uint8Array(result));
  100. }
  101. if (isBytes(value)) {
  102. return addSlice(new Uint8Array(value));
  103. }
  104. return logger.throwArgumentError("invalid arrayify value", "value", value);
  105. }
  106. export function concat(items: ReadonlyArray<BytesLike>): Uint8Array {
  107. const objects = items.map(item => arrayify(item));
  108. const length = objects.reduce((accum, item) => (accum + item.length), 0);
  109. const result = new Uint8Array(length);
  110. objects.reduce((offset, object) => {
  111. result.set(object, offset);
  112. return offset + object.length;
  113. }, 0);
  114. return addSlice(result);
  115. }
  116. export function stripZeros(value: BytesLike): Uint8Array {
  117. let result: Uint8Array = arrayify(value);
  118. if (result.length === 0) { return result; }
  119. // Find the first non-zero entry
  120. let start = 0;
  121. while (start < result.length && result[start] === 0) { start++ }
  122. // If we started with zeros, strip them
  123. if (start) {
  124. result = result.slice(start);
  125. }
  126. return result;
  127. }
  128. export function zeroPad(value: BytesLike, length: number): Uint8Array {
  129. value = arrayify(value);
  130. if (value.length > length) {
  131. logger.throwArgumentError("value out of range", "value", arguments[0]);
  132. }
  133. const result = new Uint8Array(length);
  134. result.set(value, length - value.length);
  135. return addSlice(result);
  136. }
  137. export function isHexString(value: any, length?: number): boolean {
  138. if (typeof(value) !== "string" || !value.match(/^0x[0-9A-Fa-f]*$/)) {
  139. return false
  140. }
  141. if (length && value.length !== 2 + 2 * length) { return false; }
  142. return true;
  143. }
  144. const HexCharacters: string = "0123456789abcdef";
  145. export function hexlify(value: BytesLike | Hexable | number | bigint, options?: DataOptions): string {
  146. if (!options) { options = { }; }
  147. if (typeof(value) === "number") {
  148. logger.checkSafeUint53(value, "invalid hexlify value");
  149. let hex = "";
  150. while (value) {
  151. hex = HexCharacters[value & 0xf] + hex;
  152. value = Math.floor(value / 16);
  153. }
  154. if (hex.length) {
  155. if (hex.length % 2) { hex = "0" + hex; }
  156. return "0x" + hex;
  157. }
  158. return "0x00";
  159. }
  160. if (typeof(value) === "bigint") {
  161. value = value.toString(16);
  162. if (value.length % 2) { return ("0x0" + value); }
  163. return "0x" + value;
  164. }
  165. if (options.allowMissingPrefix && typeof(value) === "string" && value.substring(0, 2) !== "0x") {
  166. value = "0x" + value;
  167. }
  168. if (isHexable(value)) { return value.toHexString(); }
  169. if (isHexString(value)) {
  170. if ((<string>value).length % 2) {
  171. if (options.hexPad === "left") {
  172. value = "0x0" + (<string>value).substring(2);
  173. } else if (options.hexPad === "right") {
  174. value += "0";
  175. } else {
  176. logger.throwArgumentError("hex data is odd-length", "value", value);
  177. }
  178. }
  179. return (<string>value).toLowerCase();
  180. }
  181. if (isBytes(value)) {
  182. let result = "0x";
  183. for (let i = 0; i < value.length; i++) {
  184. let v = value[i];
  185. result += HexCharacters[(v & 0xf0) >> 4] + HexCharacters[v & 0x0f];
  186. }
  187. return result;
  188. }
  189. return logger.throwArgumentError("invalid hexlify value", "value", value);
  190. }
  191. /*
  192. function unoddify(value: BytesLike | Hexable | number): BytesLike | Hexable | number {
  193. if (typeof(value) === "string" && value.length % 2 && value.substring(0, 2) === "0x") {
  194. return "0x0" + value.substring(2);
  195. }
  196. return value;
  197. }
  198. */
  199. export function hexDataLength(data: BytesLike) {
  200. if (typeof(data) !== "string") {
  201. data = hexlify(data);
  202. } else if (!isHexString(data) || (data.length % 2)) {
  203. return null;
  204. }
  205. return (data.length - 2) / 2;
  206. }
  207. export function hexDataSlice(data: BytesLike, offset: number, endOffset?: number): string {
  208. if (typeof(data) !== "string") {
  209. data = hexlify(data);
  210. } else if (!isHexString(data) || (data.length % 2)) {
  211. logger.throwArgumentError("invalid hexData", "value", data );
  212. }
  213. offset = 2 + 2 * offset;
  214. if (endOffset != null) {
  215. return "0x" + data.substring(offset, 2 + 2 * endOffset);
  216. }
  217. return "0x" + data.substring(offset);
  218. }
  219. export function hexConcat(items: ReadonlyArray<BytesLike>): string {
  220. let result = "0x";
  221. items.forEach((item) => {
  222. result += hexlify(item).substring(2);
  223. });
  224. return result;
  225. }
  226. export function hexValue(value: BytesLike | Hexable | number | bigint): string {
  227. const trimmed = hexStripZeros(hexlify(value, { hexPad: "left" }));
  228. if (trimmed === "0x") { return "0x0"; }
  229. return trimmed;
  230. }
  231. export function hexStripZeros(value: BytesLike): string {
  232. if (typeof(value) !== "string") { value = hexlify(value); }
  233. if (!isHexString(value)) {
  234. logger.throwArgumentError("invalid hex string", "value", value);
  235. }
  236. value = value.substring(2);
  237. let offset = 0;
  238. while (offset < value.length && value[offset] === "0") { offset++; }
  239. return "0x" + value.substring(offset);
  240. }
  241. export function hexZeroPad(value: BytesLike, length: number): string {
  242. if (typeof(value) !== "string") {
  243. value = hexlify(value);
  244. } else if (!isHexString(value)) {
  245. logger.throwArgumentError("invalid hex string", "value", value);
  246. }
  247. if (value.length > 2 * length + 2) {
  248. logger.throwArgumentError("value out of range", "value", arguments[1]);
  249. }
  250. while (value.length < 2 * length + 2) {
  251. value = "0x0" + value.substring(2);
  252. }
  253. return value;
  254. }
  255. export function splitSignature(signature: SignatureLike): Signature {
  256. const result = {
  257. r: "0x",
  258. s: "0x",
  259. _vs: "0x",
  260. recoveryParam: 0,
  261. v: 0,
  262. yParityAndS: "0x",
  263. compact: "0x"
  264. };
  265. if (isBytesLike(signature)) {
  266. let bytes: Uint8Array = arrayify(signature);
  267. // Get the r, s and v
  268. if (bytes.length === 64) {
  269. // EIP-2098; pull the v from the top bit of s and clear it
  270. result.v = 27 + (bytes[32] >> 7);
  271. bytes[32] &= 0x7f;
  272. result.r = hexlify(bytes.slice(0, 32));
  273. result.s = hexlify(bytes.slice(32, 64));
  274. } else if (bytes.length === 65) {
  275. result.r = hexlify(bytes.slice(0, 32));
  276. result.s = hexlify(bytes.slice(32, 64));
  277. result.v = bytes[64];
  278. } else {
  279. logger.throwArgumentError("invalid signature string", "signature", signature);
  280. }
  281. // Allow a recid to be used as the v
  282. if (result.v < 27) {
  283. if (result.v === 0 || result.v === 1) {
  284. result.v += 27;
  285. } else {
  286. logger.throwArgumentError("signature invalid v byte", "signature", signature);
  287. }
  288. }
  289. // Compute recoveryParam from v
  290. result.recoveryParam = 1 - (result.v % 2);
  291. // Compute _vs from recoveryParam and s
  292. if (result.recoveryParam) { bytes[32] |= 0x80; }
  293. result._vs = hexlify(bytes.slice(32, 64))
  294. } else {
  295. result.r = signature.r;
  296. result.s = signature.s;
  297. result.v = signature.v;
  298. result.recoveryParam = signature.recoveryParam;
  299. result._vs = signature._vs;
  300. // If the _vs is available, use it to populate missing s, v and recoveryParam
  301. // and verify non-missing s, v and recoveryParam
  302. if (result._vs != null) {
  303. const vs = zeroPad(arrayify(result._vs), 32);
  304. result._vs = hexlify(vs);
  305. // Set or check the recid
  306. const recoveryParam = ((vs[0] >= 128) ? 1: 0);
  307. if (result.recoveryParam == null) {
  308. result.recoveryParam = recoveryParam;
  309. } else if (result.recoveryParam !== recoveryParam) {
  310. logger.throwArgumentError("signature recoveryParam mismatch _vs", "signature", signature);
  311. }
  312. // Set or check the s
  313. vs[0] &= 0x7f;
  314. const s = hexlify(vs);
  315. if (result.s == null) {
  316. result.s = s;
  317. } else if (result.s !== s) {
  318. logger.throwArgumentError("signature v mismatch _vs", "signature", signature);
  319. }
  320. }
  321. // Use recid and v to populate each other
  322. if (result.recoveryParam == null) {
  323. if (result.v == null) {
  324. logger.throwArgumentError("signature missing v and recoveryParam", "signature", signature);
  325. } else if (result.v === 0 || result.v === 1) {
  326. result.recoveryParam = result.v;
  327. } else {
  328. result.recoveryParam = 1 - (result.v % 2);
  329. }
  330. } else {
  331. if (result.v == null) {
  332. result.v = 27 + result.recoveryParam;
  333. } else {
  334. const recId = (result.v === 0 || result.v === 1) ? result.v :(1 - (result.v % 2));
  335. if (result.recoveryParam !== recId) {
  336. logger.throwArgumentError("signature recoveryParam mismatch v", "signature", signature);
  337. }
  338. }
  339. }
  340. if (result.r == null || !isHexString(result.r)) {
  341. logger.throwArgumentError("signature missing or invalid r", "signature", signature);
  342. } else {
  343. result.r = hexZeroPad(result.r, 32);
  344. }
  345. if (result.s == null || !isHexString(result.s)) {
  346. logger.throwArgumentError("signature missing or invalid s", "signature", signature);
  347. } else {
  348. result.s = hexZeroPad(result.s, 32);
  349. }
  350. const vs = arrayify(result.s);
  351. if (vs[0] >= 128) {
  352. logger.throwArgumentError("signature s out of range", "signature", signature);
  353. }
  354. if (result.recoveryParam) { vs[0] |= 0x80; }
  355. const _vs = hexlify(vs);
  356. if (result._vs) {
  357. if (!isHexString(result._vs)) {
  358. logger.throwArgumentError("signature invalid _vs", "signature", signature);
  359. }
  360. result._vs = hexZeroPad(result._vs, 32);
  361. }
  362. // Set or check the _vs
  363. if (result._vs == null) {
  364. result._vs = _vs;
  365. } else if (result._vs !== _vs) {
  366. logger.throwArgumentError("signature _vs mismatch v and s", "signature", signature);
  367. }
  368. }
  369. result.yParityAndS = result._vs;
  370. result.compact = result.r + result.yParityAndS.substring(2);
  371. return result;
  372. }
  373. export function joinSignature(signature: SignatureLike): string {
  374. signature = splitSignature(signature);
  375. return hexlify(concat([
  376. signature.r,
  377. signature.s,
  378. (signature.recoveryParam ? "0x1c": "0x1b")
  379. ]));
  380. }