edge-currency-plugins 3.11.0 → 3.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/android/src/main/assets/edge-currency-plugins/edge-currency-plugins.js +48 -47
  2. package/lib/common/plugin/types.js +6 -0
  3. package/lib/common/utxobased/engine/ServerStates.js +196 -95
  4. package/lib/common/utxobased/engine/UtxoEngine.js +17 -4
  5. package/lib/common/utxobased/engine/UtxoWalletTools.js +15 -2
  6. package/lib/common/utxobased/engine/types.js +7 -1
  7. package/lib/common/utxobased/info/bitcoin.js +3 -3
  8. package/lib/common/utxobased/info/bitcoincash.js +1 -1
  9. package/lib/common/utxobased/info/bitcoingold.js +1 -1
  10. package/lib/common/utxobased/info/bitcoingoldtestnet.js +1 -1
  11. package/lib/common/utxobased/info/dash.js +3 -2
  12. package/lib/common/utxobased/info/digibyte.js +2 -2
  13. package/lib/common/utxobased/info/dogecoin.js +2 -2
  14. package/lib/common/utxobased/info/ecash.js +1 -1
  15. package/lib/common/utxobased/info/feathercoin.js +1 -1
  16. package/lib/common/utxobased/info/litecoin.js +4 -4
  17. package/lib/common/utxobased/info/pivx.js +3 -2
  18. package/lib/common/utxobased/info/qtum.js +2 -2
  19. package/lib/common/utxobased/info/ravencoin.js +1 -1
  20. package/lib/common/utxobased/info/smartcash.js +1 -1
  21. package/lib/common/utxobased/info/ufo.js +1 -1
  22. package/lib/common/utxobased/info/vertcoin.js +2 -1
  23. package/lib/common/utxobased/info/zcoin.js +2 -2
  24. package/lib/common/utxobased/keymanager/keymanager.js +56 -2
  25. package/lib/common/utxobased/keymanager/types.js +28 -1
  26. package/lib/common/utxobased/network/Blockbook.js +10 -2
  27. package/lib/src/common/plugin/types.d.ts +7 -1
  28. package/lib/src/common/utxobased/engine/ServerStates.d.ts +21 -0
  29. package/lib/src/common/utxobased/engine/UtxoWalletTools.d.ts +2 -0
  30. package/lib/src/common/utxobased/engine/types.d.ts +1 -0
  31. package/lib/src/common/utxobased/keymanager/keymanager.d.ts +3 -2
  32. package/lib/src/common/utxobased/keymanager/types.d.ts +19 -0
  33. package/lib/test/common/utxobased/engine/ServerStates.spec.d.ts +1 -0
  34. package/lib/test/common/utxobased/keymanager/coins/altcointestfixtures.d.ts +5 -0
  35. package/lib/test/common/utxobased/keymanager/messagePrefix.spec.d.ts +1 -0
  36. package/package.json +1 -1
@@ -63,7 +63,7 @@ const engineInfo = {
63
63
  segwit: true,
64
64
  coinType: 202,
65
65
  prefixes: {
66
- messagePrefix: ['\x18Bitcoin Signed Message:\n'],
66
+ messagePrefix: ['\x14UFO Signed Message:\n'],
67
67
  wif: [0x9b],
68
68
  legacyXPriv: [0x0488ade4],
69
69
  legacyXPub: [0x0488b21e],
@@ -48,7 +48,7 @@ const currencyInfo = {
48
48
  const engineInfo = {
49
49
  serverConfigs: [
50
50
  {
51
- type: 'blockbook-nownode',
51
+ type: 'blockbook',
52
52
  uris: ['https://vtc-wusa1.edge.app']
53
53
  }
54
54
  ],
@@ -77,6 +77,7 @@ const engineInfo = {
77
77
  segwit: true,
78
78
  coinType: 28,
79
79
  prefixes: {
80
+ // Vertcoin Core genuinely uses Bitcoin's magic; not an oversight.
80
81
  messagePrefix: ['\x18Bitcoin Signed Message:\n'],
81
82
  wif: [0x80],
82
83
  legacyXPriv: [0x0488ade4],
@@ -45,7 +45,7 @@ var _commonInfo = require('./commonInfo');
45
45
  const engineInfo = {
46
46
  serverConfigs: [
47
47
  {
48
- type: 'blockbook-nownode',
48
+ type: 'blockbook',
49
49
  uris: ['https://firo-eusa1.edge.app']
50
50
  },
51
51
  {
@@ -78,7 +78,7 @@ var _commonInfo = require('./commonInfo');
78
78
  segwit: false,
79
79
  coinType: 136,
80
80
  prefixes: {
81
- messagePrefix: ['\x18Zcoin Signed Message:\n'],
81
+ messagePrefix: ['\x16Zcoin Signed Message:\n'],
82
82
  wif: [0xd2],
83
83
  legacyXPriv: [0x0488ade4],
84
84
  legacyXPub: [0x0488b21e],
@@ -21,6 +21,12 @@ var _undefinedIfEmptyString = require('../../../util/undefinedIfEmptyString');
21
21
 
22
22
 
23
23
 
24
+
25
+
26
+
27
+
28
+
29
+
24
30
  var _bip69 = require('./bip69');
25
31
 
26
32
 
@@ -938,14 +944,62 @@ const wifToPrivateKeyEncodingInternal = (
938
944
  }).publicKey.toString('hex')
939
945
  } exports.privateKeyEncodingToPubkey = privateKeyEncodingToPubkey;
940
946
 
941
- function signMessageBase64(message, privateKey) {
947
+ /**
948
+ * The `segwitType` to hand `bitcoinjs-message`, or `undefined` to leave the
949
+ * header byte in the legacy Electrum range. Only the BIP137 encoding cares
950
+ * about the derivation purpose, so only it resolves one.
951
+ */
952
+ function getBip137SegwitType(
953
+ signatureFormat,
954
+ format
955
+ ) {
956
+ if (signatureFormat !== 'bip137') return undefined
957
+ const purposeType = bip43PurposeNumberToTypeEnum(
958
+ parseInt(format.replace('bip', ''))
959
+ )
960
+ switch (purposeType) {
961
+ case BIP43PurposeTypeEnum.Segwit:
962
+ return 'p2wpkh'
963
+ case BIP43PurposeTypeEnum.WrappedSegwit:
964
+ return 'p2sh(p2wpkh)'
965
+ default:
966
+ return undefined
967
+ }
968
+ }
969
+
970
+ function signMessageBase64(
971
+ message,
972
+ privateKey,
973
+ format,
974
+ coin,
975
+ signatureFormat = 'electrum'
976
+ ) {
942
977
  const ECPair = getECPair()
943
978
  const keyPair = ECPair.fromPrivateKey(Buffer.from(privateKey, 'hex'))
944
979
  if (keyPair.privateKey == null) {
945
980
  throw new Error('Address could not sign message')
946
981
  }
982
+ // BIP137 encodes the address type in the signature's header byte. Native
983
+ // SegWit (bip84 / P2WPKH) and nested SegWit (bip49 / P2SH-P2WPKH) each have
984
+ // their own header range; verifiers such as Bringin reject a SegWit address
985
+ // whose message was signed with a legacy (bip44) header byte. The derivation
986
+ // path is what tells us the script type, so no caller has to infer it from
987
+ // the address string. Callers opt in, since emitting a BIP137 header to a
988
+ // verifier that expects the legacy Electrum encoding is just as broken.
989
+ // The purpose lookup stays inside the BIP137 branch on purpose:
990
+ // `bip43PurposeNumberToTypeEnum` throws on a purpose it does not map, so
991
+ // computing it unconditionally would let a future `CurrencyFormat` (bip86
992
+ // Taproot being the obvious candidate) break the default Electrum path,
993
+ // which never needs it.
994
+ const segwitType = getBip137SegwitType(signatureFormat, format)
995
+ // Magic-hash the message with the coin's own prefix (e.g. "Litecoin Signed
996
+ // Message:\n") so the signature verifies against that coin's addresses.
997
+ // Every UTXO coin routes through here, not just Bitcoin.
998
+ const messagePrefix = _coinmapper.getCoinFromString.call(void 0, coin).prefixes.messagePrefix[0]
947
999
  return _bitcoinjsmessage2.default
948
- .sign(message, keyPair.privateKey, keyPair.compressed)
1000
+ .sign(message, keyPair.privateKey, keyPair.compressed, messagePrefix, {
1001
+ segwitType
1002
+ })
949
1003
  .toString('base64')
950
1004
  } exports.signMessageBase64 = signMessageBase64;
951
1005
 
@@ -1,6 +1,17 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _cleaners = require('cleaners');
2
2
  var _types = require('edge-core-js/types');
3
3
 
4
+ /**
5
+ * How to encode the header byte of a signed message.
6
+ * - `electrum`: the legacy format, whose header carries only the recovery id
7
+ * and key compression. Every verifier understands it, but it says nothing
8
+ * about the script type.
9
+ * - `bip137`: additionally encodes the signing address' script type, which
10
+ * verifiers such as Bringin require for SegWit addresses.
11
+ */
12
+ const asUtxoSignatureFormat = _cleaners.asValue.call(void 0, 'electrum', 'bip137'); exports.asUtxoSignatureFormat = asUtxoSignatureFormat
13
+
14
+
4
15
 
5
16
 
6
17
 
@@ -27,6 +38,22 @@ function asMaybeError(name) {
27
38
  }
28
39
  }
29
40
 
41
+ /**
42
+ * The wallet cannot sign for the requested address: either it does not derive
43
+ * it, or the string is not an address of this chain at all. Callers get a
44
+ * stable `name` to branch on instead of matching against the message prose.
45
+ */
46
+ class AddressNotOwnedError extends Error {
47
+ constructor() {
48
+ super('Wallet does not own the address to sign with')
49
+ this.name = 'AddressNotOwnedError'
50
+ }
51
+ } exports.AddressNotOwnedError = AddressNotOwnedError;
52
+
53
+ const asMaybeAddressNotOwnedError = asMaybeError(
54
+ 'AddressNotOwnedError'
55
+ ); exports.asMaybeAddressNotOwnedError = asMaybeAddressNotOwnedError
56
+
30
57
  const asMaybeInsufficientFundsErrorPlus = asMaybeError(
31
58
  // Share the same name because this error type is a subtype of
32
59
  // InsufficientFundsError and therefore backwards compatible
@@ -177,8 +177,16 @@ var _Socket = require('./Socket');
177
177
 
178
178
 
179
179
  {
180
- const value = yield { ...message }
181
- deferred.resolve(value)
180
+ try {
181
+ const value = yield { ...message }
182
+ deferred.resolve(value)
183
+ } catch (error) {
184
+ // The socket reports a failed request (an error response, the
185
+ // request timeout, the socket closing) by throwing into this
186
+ // generator. Without this catch the deferred stays pending forever
187
+ // and the caller hangs instead of seeing the failure.
188
+ deferred.reject(error)
189
+ }
182
190
  return false
183
191
  }
184
192
  const generator = taskGeneratorFn()
@@ -116,7 +116,13 @@ export interface EngineInfo {
116
116
  txSpecificHandling?: (tx: TransactionData, specialTx: unknown) => TransactionData;
117
117
  }
118
118
  export interface ServerConfig {
119
- type: 'blockbook-nownode';
119
+ /**
120
+ * - `blockbook`: a public Blockbook HTTP server. No credentials are sent.
121
+ * - `blockbook-nownode`: a NOWNodes Blockbook HTTP server. Requests carry
122
+ * the `nowNodesApiKey` init option as the `api-key` header, and the
123
+ * server is skipped when no key is configured.
124
+ */
125
+ type: 'blockbook' | 'blockbook-nownode';
120
126
  uris: string[];
121
127
  }
122
128
  /**
@@ -23,6 +23,11 @@ interface ServerStateConfig {
23
23
  pluginInfo: PluginInfo;
24
24
  pluginState: PluginState;
25
25
  walletInfo: SafeWalletInfo;
26
+ /**
27
+ * Upper bound on any single broadcast attempt. Defaults to the same 30
28
+ * seconds the socket layer uses for a request. Exposed for tests.
29
+ */
30
+ broadcastTimeoutMs?: number;
26
31
  }
27
32
  export interface ServerStates {
28
33
  setPickNextTaskCB: (callback: (uri: string) => AsyncGenerator<WsTask<unknown> | boolean>) => void;
@@ -46,5 +51,21 @@ export interface ServerStates {
46
51
  transactionSpecialQueryTask: (serverUri: string, txId: string) => WsTaskGenerator<unknown>;
47
52
  utxoListQueryTask: (serverUri: string, address: string) => WsTaskGenerator<AddressUtxosResponse>;
48
53
  }
54
+ /**
55
+ * How long broadcastTx gives the first wave (our sockets and Edge's own HTTP
56
+ * servers) before it also sends the transaction to the NOWNodes HTTP servers.
57
+ * NOWNodes is a third party, so it should only see a transaction when our own
58
+ * infrastructure has not already carried it. A first wave that fails outright
59
+ * does not wait this long; the second wave fires as soon as it has all failed.
60
+ */
61
+ export declare const NOWNODES_BROADCAST_DELAY_MS = 2000;
62
+ /**
63
+ * Every broadcast attempt is counted toward "all attempts failed", so every
64
+ * attempt must settle. Sockets already expire a request after 30 seconds
65
+ * (Socket.ts); HTTP has no such bound of its own, and a server that accepts
66
+ * the connection and never answers would otherwise hold the whole broadcast
67
+ * open. This mirrors the socket figure.
68
+ */
69
+ export declare const BROADCAST_ATTEMPT_TIMEOUT_MS = 30000;
49
70
  export declare function makeServerStates(config: ServerStateConfig): ServerStates;
50
71
  export {};
@@ -2,6 +2,7 @@ import { AddressPath, ChangePath, CurrencyFormat, PluginInfo } from '../../plugi
2
2
  import { ScriptTemplate } from '../info/scriptTemplates/types';
3
3
  import { PublicKey } from '../keymanager/cleaners';
4
4
  import { PrivateKeyEncoding } from '../keymanager/keymanager';
5
+ import { UtxoSignatureFormat } from '../keymanager/types';
5
6
  import { CurrencyFormatKeys } from './utils';
6
7
  export interface WalletToolsConfig {
7
8
  pluginInfo: PluginInfo;
@@ -48,6 +49,7 @@ interface SignMessageArgs {
48
49
  path: AddressPath;
49
50
  message: string;
50
51
  xprivKeys: CurrencyFormatKeys;
52
+ signatureFormat: UtxoSignatureFormat;
51
53
  }
52
54
  export declare function makeUtxoWalletTools(config: WalletToolsConfig): UtxoWalletTools;
53
55
  export {};
@@ -23,6 +23,7 @@ export interface UtxoTxOtherParams {
23
23
  export declare type UtxoSignMessageOtherParams = ReturnType<typeof asUtxoSignMessageOtherParams>;
24
24
  export declare const asUtxoSignMessageOtherParams: import("cleaners").ObjectCleaner<{
25
25
  publicAddress: string;
26
+ signatureFormat: "electrum" | "bip137";
26
27
  }>;
27
28
  export declare type UtxoSpendInfoOtherParams = ReturnType<typeof asUtxoSpendInfoOtherParams>;
28
29
  export declare const asUtxoSpendInfoOtherParams: import("cleaners").ObjectCleaner<{
@@ -1,7 +1,8 @@
1
1
  import { EdgeLog, EdgeMemo } from 'edge-core-js/types';
2
- import { ChangePath, FeeInfo } from '../../plugin/types';
2
+ import { ChangePath, CurrencyFormat, FeeInfo } from '../../plugin/types';
3
3
  import { UtxoData } from '../db/types';
4
4
  import { ScriptTemplate, ScriptTemplates } from '../info/scriptTemplates/types';
5
+ import { UtxoSignatureFormat } from './types';
5
6
  import * as utxopicker from './utxopicker';
6
7
  export declare enum BIP43PurposeTypeEnum {
7
8
  Airbitz = "airbitz",
@@ -202,7 +203,7 @@ export declare const xprivToPrivateKey: (args: XPrivToPrivateKeyArgs) => string;
202
203
  export declare function privateKeyToWIF(args: PrivateKeyToWIFArgs): string;
203
204
  export declare const wifToPrivateKeyEncoding: (args: WIFToPrivateKeyEncodingArgs) => PrivateKeyEncoding;
204
205
  export declare function privateKeyEncodingToPubkey(privateKeyEncoding: PrivateKeyEncoding): string;
205
- export declare function signMessageBase64(message: string, privateKey: string): string;
206
+ export declare function signMessageBase64(message: string, privateKey: string, format: CurrencyFormat, coin: string, signatureFormat?: UtxoSignatureFormat): string;
206
207
  export declare function makeTx(args: MakeTxArgs): MakeTxReturn;
207
208
  export declare function signTx(args: SignTxArgs): Promise<SignTxReturn>;
208
209
  export {};
@@ -1,5 +1,15 @@
1
1
  import { Cleaner } from 'cleaners';
2
2
  import { EdgeTokenId, InsufficientFundsError } from 'edge-core-js/types';
3
+ /**
4
+ * How to encode the header byte of a signed message.
5
+ * - `electrum`: the legacy format, whose header carries only the recovery id
6
+ * and key compression. Every verifier understands it, but it says nothing
7
+ * about the script type.
8
+ * - `bip137`: additionally encodes the signing address' script type, which
9
+ * verifiers such as Bringin require for SegWit addresses.
10
+ */
11
+ export declare const asUtxoSignatureFormat: Cleaner<"electrum" | "bip137">;
12
+ export declare type UtxoSignatureFormat = ReturnType<typeof asUtxoSignatureFormat>;
3
13
  interface InsufficientFundsErrorOptsPlus {
4
14
  tokenId: EdgeTokenId;
5
15
  networkFee?: string;
@@ -9,5 +19,14 @@ export declare class InsufficientFundsErrorPlus extends InsufficientFundsError {
9
19
  networkFeeShortage?: string;
10
20
  constructor(opts: InsufficientFundsErrorOptsPlus);
11
21
  }
22
+ /**
23
+ * The wallet cannot sign for the requested address: either it does not derive
24
+ * it, or the string is not an address of this chain at all. Callers get a
25
+ * stable `name` to branch on instead of matching against the message prose.
26
+ */
27
+ export declare class AddressNotOwnedError extends Error {
28
+ constructor();
29
+ }
30
+ export declare const asMaybeAddressNotOwnedError: Cleaner<AddressNotOwnedError | undefined>;
12
31
  export declare const asMaybeInsufficientFundsErrorPlus: Cleaner<InsufficientFundsErrorPlus | undefined>;
13
32
  export {};
@@ -1,4 +1,6 @@
1
+ import { CurrencyFormat } from '../../../../../src/common/plugin/types';
1
2
  import { AddressTypeEnum, BIP43PurposeTypeEnum, ScriptTypeEnum } from '../../../../../src/common/utxobased/keymanager/keymanager';
3
+ import { UtxoSignatureFormat } from '../../../../../src/common/utxobased/keymanager/types';
2
4
  interface SeedToXPrivTests {
3
5
  xpriv: string;
4
6
  type: BIP43PurposeTypeEnum;
@@ -34,6 +36,9 @@ interface XPubToPubkeyTests {
34
36
  interface SignMessageTests {
35
37
  wif: string;
36
38
  message: string;
39
+ format: CurrencyFormat;
40
+ /** Omitted to exercise the default, which must stay `electrum`. */
41
+ signatureFormat?: UtxoSignatureFormat;
37
42
  signature: string;
38
43
  }
39
44
  interface Coin {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edge-currency-plugins",
3
- "version": "3.11.0",
3
+ "version": "3.13.0",
4
4
  "description": "Edge currency plugins to fetch and process wallet accounts, balances and transactions.",
5
5
  "homepage": "https://edge.app/",
6
6
  "repository": "https://github.com/EdgeApp/edge-currency-plugins",