signature.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. import { ZeroHash } from "../constants/index.js";
  2. import {
  3. concat, dataLength, getBigInt, getBytes, getNumber, hexlify,
  4. toBeArray, isHexString, zeroPadValue,
  5. assertArgument, assertPrivate
  6. } from "../utils/index.js";
  7. import type {
  8. BigNumberish, BytesLike, Numeric
  9. } from "../utils/index.js";
  10. // Constants
  11. const BN_0 = BigInt(0);
  12. const BN_1 = BigInt(1);
  13. const BN_2 = BigInt(2);
  14. const BN_27 = BigInt(27);
  15. const BN_28 = BigInt(28);
  16. const BN_35 = BigInt(35);
  17. const _guard = { };
  18. // @TODO: Allow Uint8Array
  19. /**
  20. * A SignatureLike
  21. *
  22. * @_docloc: api/crypto:Signing
  23. */
  24. export type SignatureLike = Signature | string | {
  25. r: string;
  26. s: string;
  27. v: BigNumberish;
  28. yParity?: 0 | 1;
  29. yParityAndS?: string;
  30. } | {
  31. r: string;
  32. yParityAndS: string;
  33. yParity?: 0 | 1;
  34. s?: string;
  35. v?: number;
  36. } | {
  37. r: string;
  38. s: string;
  39. yParity: 0 | 1;
  40. v?: BigNumberish;
  41. yParityAndS?: string;
  42. };
  43. function toUint256(value: BigNumberish): string {
  44. return zeroPadValue(toBeArray(value), 32);
  45. }
  46. /**
  47. * A Signature @TODO
  48. *
  49. *
  50. * @_docloc: api/crypto:Signing
  51. */
  52. export class Signature {
  53. #r: string;
  54. #s: string;
  55. #v: 27 | 28;
  56. #networkV: null | bigint;
  57. /**
  58. * The ``r`` value for a signautre.
  59. *
  60. * This represents the ``x`` coordinate of a "reference" or
  61. * challenge point, from which the ``y`` can be computed.
  62. */
  63. get r(): string { return this.#r; }
  64. set r(value: BytesLike) {
  65. assertArgument(dataLength(value) === 32, "invalid r", "value", value);
  66. this.#r = hexlify(value);
  67. }
  68. /**
  69. * The ``s`` value for a signature.
  70. */
  71. get s(): string { return this.#s; }
  72. set s(_value: BytesLike) {
  73. assertArgument(dataLength(_value) === 32, "invalid s", "value", _value);
  74. const value = hexlify(_value);
  75. assertArgument(parseInt(value.substring(0, 3)) < 8, "non-canonical s", "value", value);
  76. this.#s = value;
  77. }
  78. /**
  79. * The ``v`` value for a signature.
  80. *
  81. * Since a given ``x`` value for ``r`` has two possible values for
  82. * its correspondin ``y``, the ``v`` indicates which of the two ``y``
  83. * values to use.
  84. *
  85. * It is normalized to the values ``27`` or ``28`` for legacy
  86. * purposes.
  87. */
  88. get v(): 27 | 28 { return this.#v; }
  89. set v(value: BigNumberish) {
  90. const v = getNumber(value, "value");
  91. assertArgument(v === 27 || v === 28, "invalid v", "v", value);
  92. this.#v = v;
  93. }
  94. /**
  95. * The EIP-155 ``v`` for legacy transactions. For non-legacy
  96. * transactions, this value is ``null``.
  97. */
  98. get networkV(): null | bigint { return this.#networkV; }
  99. /**
  100. * The chain ID for EIP-155 legacy transactions. For non-legacy
  101. * transactions, this value is ``null``.
  102. */
  103. get legacyChainId(): null | bigint {
  104. const v = this.networkV;
  105. if (v == null) { return null; }
  106. return Signature.getChainId(v);
  107. }
  108. /**
  109. * The ``yParity`` for the signature.
  110. *
  111. * See ``v`` for more details on how this value is used.
  112. */
  113. get yParity(): 0 | 1 {
  114. return (this.v === 27) ? 0: 1;
  115. }
  116. /**
  117. * The [[link-eip-2098]] compact representation of the ``yParity``
  118. * and ``s`` compacted into a single ``bytes32``.
  119. */
  120. get yParityAndS(): string {
  121. // The EIP-2098 compact representation
  122. const yParityAndS = getBytes(this.s);
  123. if (this.yParity) { yParityAndS[0] |= 0x80; }
  124. return hexlify(yParityAndS);
  125. }
  126. /**
  127. * The [[link-eip-2098]] compact representation.
  128. */
  129. get compactSerialized(): string {
  130. return concat([ this.r, this.yParityAndS ]);
  131. }
  132. /**
  133. * The serialized representation.
  134. */
  135. get serialized(): string {
  136. return concat([ this.r, this.s, (this.yParity ? "0x1c": "0x1b") ]);
  137. }
  138. /**
  139. * @private
  140. */
  141. constructor(guard: any, r: string, s: string, v: 27 | 28) {
  142. assertPrivate(guard, _guard, "Signature");
  143. this.#r = r;
  144. this.#s = s;
  145. this.#v = v;
  146. this.#networkV = null;
  147. }
  148. [Symbol.for('nodejs.util.inspect.custom')](): string {
  149. return `Signature { r: "${ this.r }", s: "${ this.s }", yParity: ${ this.yParity }, networkV: ${ this.networkV } }`;
  150. }
  151. /**
  152. * Returns a new identical [[Signature]].
  153. */
  154. clone(): Signature {
  155. const clone = new Signature(_guard, this.r, this.s, this.v);
  156. if (this.networkV) { clone.#networkV = this.networkV; }
  157. return clone;
  158. }
  159. /**
  160. * Returns a representation that is compatible with ``JSON.stringify``.
  161. */
  162. toJSON(): any {
  163. const networkV = this.networkV;
  164. return {
  165. _type: "signature",
  166. networkV: ((networkV != null) ? networkV.toString(): null),
  167. r: this.r, s: this.s, v: this.v,
  168. };
  169. }
  170. /**
  171. * Compute the chain ID from the ``v`` in a legacy EIP-155 transactions.
  172. *
  173. * @example:
  174. * Signature.getChainId(45)
  175. * //_result:
  176. *
  177. * Signature.getChainId(46)
  178. * //_result:
  179. */
  180. static getChainId(v: BigNumberish): bigint {
  181. const bv = getBigInt(v, "v");
  182. // The v is not an EIP-155 v, so it is the unspecified chain ID
  183. if ((bv == BN_27) || (bv == BN_28)) { return BN_0; }
  184. // Bad value for an EIP-155 v
  185. assertArgument(bv >= BN_35, "invalid EIP-155 v", "v", v);
  186. return (bv - BN_35) / BN_2;
  187. }
  188. /**
  189. * Compute the ``v`` for a chain ID for a legacy EIP-155 transactions.
  190. *
  191. * Legacy transactions which use [[link-eip-155]] hijack the ``v``
  192. * property to include the chain ID.
  193. *
  194. * @example:
  195. * Signature.getChainIdV(5, 27)
  196. * //_result:
  197. *
  198. * Signature.getChainIdV(5, 28)
  199. * //_result:
  200. *
  201. */
  202. static getChainIdV(chainId: BigNumberish, v: 27 | 28): bigint {
  203. return (getBigInt(chainId) * BN_2) + BigInt(35 + v - 27);
  204. }
  205. /**
  206. * Compute the normalized legacy transaction ``v`` from a ``yParirty``,
  207. * a legacy transaction ``v`` or a legacy [[link-eip-155]] transaction.
  208. *
  209. * @example:
  210. * // The values 0 and 1 imply v is actually yParity
  211. * Signature.getNormalizedV(0)
  212. * //_result:
  213. *
  214. * // Legacy non-EIP-1559 transaction (i.e. 27 or 28)
  215. * Signature.getNormalizedV(27)
  216. * //_result:
  217. *
  218. * // Legacy EIP-155 transaction (i.e. >= 35)
  219. * Signature.getNormalizedV(46)
  220. * //_result:
  221. *
  222. * // Invalid values throw
  223. * Signature.getNormalizedV(5)
  224. * //_error:
  225. */
  226. static getNormalizedV(v: BigNumberish): 27 | 28 {
  227. const bv = getBigInt(v);
  228. if (bv === BN_0 || bv === BN_27) { return 27; }
  229. if (bv === BN_1 || bv === BN_28) { return 28; }
  230. assertArgument(bv >= BN_35, "invalid v", "v", v);
  231. // Otherwise, EIP-155 v means odd is 27 and even is 28
  232. return (bv & BN_1) ? 27: 28;
  233. }
  234. /**
  235. * Creates a new [[Signature]].
  236. *
  237. * If no %%sig%% is provided, a new [[Signature]] is created
  238. * with default values.
  239. *
  240. * If %%sig%% is a string, it is parsed.
  241. */
  242. static from(sig?: SignatureLike): Signature {
  243. function assertError(check: unknown, message: string): asserts check {
  244. assertArgument(check, message, "signature", sig);
  245. };
  246. if (sig == null) {
  247. return new Signature(_guard, ZeroHash, ZeroHash, 27);
  248. }
  249. if (typeof(sig) === "string") {
  250. const bytes = getBytes(sig, "signature");
  251. if (bytes.length === 64) {
  252. const r = hexlify(bytes.slice(0, 32));
  253. const s = bytes.slice(32, 64);
  254. const v = (s[0] & 0x80) ? 28: 27;
  255. s[0] &= 0x7f;
  256. return new Signature(_guard, r, hexlify(s), v);
  257. }
  258. if (bytes.length === 65) {
  259. const r = hexlify(bytes.slice(0, 32));
  260. const s = bytes.slice(32, 64);
  261. assertError((s[0] & 0x80) === 0, "non-canonical s");
  262. const v = Signature.getNormalizedV(bytes[64]);
  263. return new Signature(_guard, r, hexlify(s), v);
  264. }
  265. assertError(false, "invalid raw signature length");
  266. }
  267. if (sig instanceof Signature) { return sig.clone(); }
  268. // Get r
  269. const _r = sig.r;
  270. assertError(_r != null, "missing r");
  271. const r = toUint256(_r);
  272. // Get s; by any means necessary (we check consistency below)
  273. const s = (function(s?: string, yParityAndS?: string) {
  274. if (s != null) { return toUint256(s); }
  275. if (yParityAndS != null) {
  276. assertError(isHexString(yParityAndS, 32), "invalid yParityAndS");
  277. const bytes = getBytes(yParityAndS);
  278. bytes[0] &= 0x7f;
  279. return hexlify(bytes);
  280. }
  281. assertError(false, "missing s");
  282. })(sig.s, sig.yParityAndS);
  283. assertError((getBytes(s)[0] & 0x80) == 0, "non-canonical s");
  284. // Get v; by any means necessary (we check consistency below)
  285. const { networkV, v } = (function(_v?: BigNumberish, yParityAndS?: string, yParity?: Numeric): { networkV?: bigint, v: 27 | 28 } {
  286. if (_v != null) {
  287. const v = getBigInt(_v);
  288. return {
  289. networkV: ((v >= BN_35) ? v: undefined),
  290. v: Signature.getNormalizedV(v)
  291. };
  292. }
  293. if (yParityAndS != null) {
  294. assertError(isHexString(yParityAndS, 32), "invalid yParityAndS");
  295. return { v: ((getBytes(yParityAndS)[0] & 0x80) ? 28: 27) };
  296. }
  297. if (yParity != null) {
  298. switch (getNumber(yParity, "sig.yParity")) {
  299. case 0: return { v: 27 };
  300. case 1: return { v: 28 };
  301. }
  302. assertError(false, "invalid yParity");
  303. }
  304. assertError(false, "missing v");
  305. })(sig.v, sig.yParityAndS, sig.yParity);
  306. const result = new Signature(_guard, r, s, v);
  307. if (networkV) { result.#networkV = networkV; }
  308. // If multiple of v, yParity, yParityAndS we given, check they match
  309. assertError(sig.yParity == null || getNumber(sig.yParity, "sig.yParity") === result.yParity, "yParity mismatch");
  310. assertError(sig.yParityAndS == null || sig.yParityAndS === result.yParityAndS, "yParityAndS mismatch");
  311. return result;
  312. }
  313. }