@sidhujag/sysweb3-keyring 2.0.0 → 2.1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.1.0
4
+
5
+ - Add the standalone `cjs/public-bip84` entry for strict Bitcoin/Syscoin mainnet account parsing and native SegWit address derivation. It returns public data only; an account public key does not prove ancestry or ownership.
6
+ - Add `SoftwareKeyring.deriveBip84HdPublicAccount` for guarded public derivation from an unlocked software HD wallet, without selecting accounts, writing storage or discovering addresses.
7
+ - Add the standalone `cjs/bip84-native` planner for bounded native-coin P2WPKH transactions with explicit parent data, change, RBF and exact fee limits. Asset, special and coinbase parents are excluded.
8
+ - Add `SoftwareKeyring.signPreparedBip84Transaction` for the selected Bitcoin/Syscoin mainnet HD account. It binds the account, network and synchronous host guard, verifies the rebuilt transaction and signatures, and returns immutable signed bytes, identifiers and exact fee/size data.
9
+ - These offline APIs do not unlock, access a provider, persist records or broadcast. Hosts still own input eligibility, fresh approval, change reservations and durable journaling before broadcast. Existing vault, EVM, generic PSBT and hardware APIs remain unchanged; native and live qualification remain separate.
10
+
3
11
  ## 2.0.0
4
12
 
5
13
  - Add `SoftwareKeyring.deriveEvmHdRuntimeAccount` for host-allocated Ethereum BIP44 indices. It returns the existing password-encrypted runtime account record without installing an account, writing storage or querying a network. The method binds session, selection and network identity and requires a synchronous host guard. Hosts must strip encrypted `xprv` before producing public metadata.
package/README.md CHANGED
@@ -74,6 +74,114 @@ const keyringManager = await KeyringManager.createInitialized(
74
74
  );
75
75
  ```
76
76
 
77
+ ### Offline BIP84 public account derivation
78
+
79
+ The standalone public entry accepts a bare **mainnet xpub or zpub** for a depth-3
80
+ account whose child number is hardened. It derives only native SegWit P2WPKH
81
+ addresses at `/0/index` (receive) or `/1/index` (change), with a nonhardened index
82
+ from 0 through 2,147,483,647. Choose `bitcoin` or `syscoin` explicitly. Testnet
83
+ versions, private keys, descriptors, origin/path suffixes and arbitrary networks
84
+ are rejected. Requests must be plain own-data objects with exactly the documented
85
+ fields; inherited properties, getters and extra fields are rejected. Existing
86
+ generic watch-descriptor and keyring APIs are unchanged.
87
+
88
+ ```typescript
89
+ import {
90
+ parseBip84PublicAccount,
91
+ deriveBip84PublicAddress,
92
+ } from '@sidhujag/sysweb3-keyring/cjs/public-bip84';
93
+
94
+ const account = parseBip84PublicAccount({
95
+ network: 'syscoin',
96
+ accountKey: exportedPublicAccountKey,
97
+ });
98
+ const receive = deriveBip84PublicAddress({
99
+ network: account.network,
100
+ accountKey: account.canonicalKey,
101
+ branch: 0,
102
+ index: 0,
103
+ });
104
+ // receive.address, receive.scriptPubKey; no keyring or provider required.
105
+ ```
106
+
107
+ The canonical account identity is the tuple `(network, policy, canonicalKey)`.
108
+ `policy` is `bip84-p2wpkh`; `canonicalKey` normalizes xpub/zpub aliases to zpub.
109
+ `accountIndex` is the encoded hardened child number minus 2^31. Key serialization
110
+ and depth cannot prove the original purpose, coin type, full ancestry or ownership.
111
+ In particular, the same public key can be encoded for either supported network;
112
+ network selection changes the address encoding and identity, not the derived
113
+ witness program. Require the user to verify their source wallet's BIP84 account
114
+ and network, and compare a known receive address before using a watch account.
115
+
116
+ Calls are synchronous and stateless: one key of at most 112 Base58 characters,
117
+ one account parse and two requested child derivations per address; no range scan,
118
+ cache, fetch, storage, RNG or wallet signing. The upstream BIP32 factory runs
119
+ deterministic ECC known-vector self-tests at module initialization (including
120
+ fixed-fixture signatures); it never receives a wallet private key here. The
121
+ underlying BIP32 implementation can retry an invalid child internally; a returned index mismatch is rejected instead
122
+ of silently changing the requested path. There is no wall-clock guarantee for
123
+ pathological library retries. Successful outputs are frozen objects containing
124
+ only strings and numbers (including a frozen nested account), never a BIP32 node.
125
+ Invalid input throws `PublicBip84Error` with a constant `INVALID_INPUT`,
126
+ `INVALID_ACCOUNT` or `INVALID_DERIVATION` code and no echoed key.
127
+
128
+ An account public key reveals all nonhardened receive and change addresses and
129
+ allows their activity to be linked. It cannot spend funds on its own, but remains
130
+ privacy-sensitive. The API performs no discovery or history lookup. A consuming
131
+ wallet must obtain informed consent before sharing an account key or derived
132
+ addresses with an indexer, own explicit discovery/page/concurrency bounds, and
133
+ label partial or unavailable history honestly. This library slice does not
134
+ implement or qualify a complete mobile watch-account feature.
135
+
136
+ ### Development BIP84 native-transfer foundation
137
+
138
+ `SoftwareKeyring.deriveBip84HdPublicAccount(request, { assertCurrent })` derives
139
+ an explicit HD index for a captured Bitcoin/Syscoin mainnet context and returns
140
+ only frozen public account and receive-address data. It requires an already
141
+ unlocked software host; it does not select accounts, update a catalogue, store an
142
+ xprv, discover addresses or contact a provider. The host must supply a synchronous
143
+ revision/consent guard and recheck it after awaiting the operation. Labels and
144
+ currency strings do not choose the derivation path.
145
+
146
+ The standalone `@sidhujag/sysweb3-keyring/cjs/bip84-native` entry exports
147
+ `prepareBip84NativeTransaction` and its types. It builds exact unsigned version-2
148
+ bytes from a bounded immutable plan: one P2WPKH recipient, optional verified
149
+ branch-1 change, explicit RBF, locktime zero, full previous transaction data and
150
+ integer fee limits. Parent transactions must be ordinary version 1/2 and
151
+ non-coinbase. All SYS special/asset parents are excluded even when a selected
152
+ output appears to be native change. Missing provider asset metadata is not proof
153
+ that an output is safe to spend.
154
+
155
+ The planner allows at most 64 inputs/parents, 1,000,000 bytes per parent and
156
+ 2,000,000 aggregate parent bytes. Before decoding any parent, it also enforces
157
+ an aggregate budget of 8,192 decoded components: parent inputs, outputs, witness
158
+ vectors and witness items each consume one component, including empty items.
159
+ This is a wallet resource policy, not a consensus limit; large scripts remain
160
+ subject to the separate byte limits. Wallet policy additionally caps absolute fees
161
+ at 10,000,000 satoshis and fee rate at 1,000 sat/vB; callers must provide limits
162
+ no higher than those ceilings. Maximum signature size is used only to reject
163
+ fees that cannot possibly satisfy the rate ceiling; the prepared signer checks
164
+ that ceiling again using the actual finalized virtual size. Zero fee is arithmetic-valid, not a relay guarantee.
165
+ Oversized valid parents are unavailable to this initial planner; no data is
166
+ truncated or silently omitted. Returned bytes/data are public and privacy-sensitive.
167
+
168
+ This is a development foundation, **not a native UTXO sending capability**.
169
+ `SoftwareKeyring.signPreparedBip84Transaction(request, { assertCurrent })` (added in 2.1.0)
170
+ signs only the selected HD account's exact prepared plan. It requires the SDK
171
+ selected type/index, requested account and seed-derived canonical public key to
172
+ agree. The mandatory synchronous host guard additionally binds fresh approval,
173
+ monotonic revision and review expiry. It rebuilds the plan, verifies complete
174
+ low-S SIGHASH_ALL signatures and exact unsigned bytes, and checks the final fee
175
+ against actual virtual size. It performs no unlock, storage, discovery or
176
+ broadcast and returns only frozen signed bytes, identifiers and fee/size data.
177
+ It does not consume or create per-account encrypted xprvs. The host still owns
178
+ canonical-chain/unspent checks, confirmed-input eligibility, protected collateral
179
+ and pending outpoint reservations, change-index persistence, fresh authentication,
180
+ review cancellation and eventual journal-before-broadcast behavior. Full parent
181
+ bytes prove an outpoint's content, not chain membership, maturity or spendability.
182
+ Existing generic PSBT, EVM and hardware APIs are unchanged. Native and live
183
+ qualification remain future work.
184
+
77
185
  ### Account Management
78
186
 
79
187
  ```javascript
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prepareBip84NativeTransaction = exports.Bip84NativeError = void 0;
4
+ /** Pure native-coin BIP84 transfer preparation, without a wallet or provider. */
5
+ var bip84_native_plan_1 = require("./transactions/bip84-native-plan");
6
+ Object.defineProperty(exports, "Bip84NativeError", { enumerable: true, get: function () { return bip84_native_plan_1.Bip84NativeError; } });
7
+ Object.defineProperty(exports, "prepareBip84NativeTransaction", { enumerable: true, get: function () { return bip84_native_plan_1.prepareBip84NativeTransaction; } });
8
+ //# sourceMappingURL=bip84-native.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bip84-native.js","sourceRoot":"","sources":["../../src/bip84-native.ts"],"names":[],"mappings":";;;AAAA,iFAAiF;AACjF,sEAG0C;AAFxC,qHAAA,gBAAgB,OAAA;AAChB,kIAAA,6BAA6B,OAAA"}
@@ -59,11 +59,15 @@ const SLH_DSA_PARAMETER_SET = 'SLH-DSA-SHA2-128-24';
59
59
  const ethers_v6_1 = require("./ethers-v6");
60
60
  const initial_state_1 = require("./initial-state");
61
61
  const private_records_1 = require("./private-records");
62
+ const public_bip84_1 = require("./public-bip84");
62
63
  const signers_1 = require("./signers");
63
64
  const software_hd_account_1 = require("./software-hd-account");
65
+ const software_utxo_account_1 = require("./software-utxo-account");
64
66
  const storage_core_1 = require("./storage-core");
67
+ const bip84_native_plan_1 = require("./transactions/bip84-native-plan");
65
68
  const ethereum_1 = require("./transactions/ethereum");
66
69
  const evm_local_signer_1 = require("./transactions/evm-local-signer");
70
+ const prepared_bip84_1 = require("./transactions/prepared-bip84");
67
71
  const prepared_evm_1 = require("./transactions/prepared-evm");
68
72
  const syscoin_core_1 = require("./transactions/syscoin-core");
69
73
  const types_1 = require("./types");
@@ -1796,6 +1800,228 @@ class KeyringCore {
1796
1800
  return { isValid: false, message: error.message };
1797
1801
  }
1798
1802
  }
1803
+ /** Fixed-profile public UTXO derivation; private material remains operation-local. */
1804
+ async deriveSoftwareBip84HdPublicAccount(input, options) {
1805
+ if (this.bip84Operation)
1806
+ throw new bip84_native_plan_1.Bip84NativeError('BUSY');
1807
+ const operation = {};
1808
+ this.bip84Operation = operation;
1809
+ const nodes = [];
1810
+ try {
1811
+ const session = this.sessionPassword;
1812
+ const mnemonic = this.sessionMnemonic;
1813
+ const revision = this.privateRecordSessionRevision;
1814
+ const vault = this.getVault();
1815
+ const selected = { ...vault.activeAccount };
1816
+ const selectedAccount = vault.accounts[selected.type]?.[selected.id];
1817
+ const identity = {
1818
+ address: selectedAccount?.address,
1819
+ xpub: selectedAccount?.xpub,
1820
+ };
1821
+ const network = { ...vault.activeNetwork };
1822
+ const request = (0, software_utxo_account_1.snapshotBip84HdPublicRequest)(input);
1823
+ const hostGuard = (0, bip84_native_plan_1.snapshotBip84Guard)(options);
1824
+ const profile = (0, bip84_native_plan_1.bip84Profile)(request.network.network);
1825
+ const assertCurrent = () => {
1826
+ const current = this.getVault();
1827
+ const account = current.accounts[selected.type]?.[selected.id];
1828
+ if (!session ||
1829
+ session.isCleared() ||
1830
+ this.sessionPassword !== session ||
1831
+ !mnemonic ||
1832
+ mnemonic.isCleared() ||
1833
+ this.sessionMnemonic !== mnemonic ||
1834
+ this.privateRecordSessionRevision !== revision ||
1835
+ this.bip84Operation !== operation ||
1836
+ selected.type !== types_1.KeyringAccountType.HDAccount ||
1837
+ current.activeAccount.type !== selected.type ||
1838
+ current.activeAccount.id !== selected.id ||
1839
+ account?.address !== identity.address ||
1840
+ account?.xpub !== identity.xpub ||
1841
+ account?.isLedgerWallet ||
1842
+ account?.isTrezorWallet ||
1843
+ account?.isSmartAccount ||
1844
+ network.kind !== sysweb3_network_1.INetworkType.Syscoin ||
1845
+ network.chainId !== profile.chainId ||
1846
+ network.slip44 !== profile.slip44 ||
1847
+ network.url !== request.network.endpoint ||
1848
+ current.activeNetwork.kind !== network.kind ||
1849
+ current.activeNetwork.chainId !== network.chainId ||
1850
+ current.activeNetwork.slip44 !== network.slip44 ||
1851
+ current.activeNetwork.url !== network.url)
1852
+ throw new bip84_native_plan_1.Bip84NativeError('CANCELLED');
1853
+ };
1854
+ const guard = () => {
1855
+ assertCurrent();
1856
+ hostGuard();
1857
+ assertCurrent();
1858
+ };
1859
+ guard();
1860
+ const config = (0, sysweb3_network_1.getNetworkConfig)(profile.slip44, profile.currency);
1861
+ const { hd } = (0, signers_1.getSyscoinSigners)({
1862
+ mnemonic: this.getDecryptedMnemonic(),
1863
+ rpc: {
1864
+ formattedNetwork: { ...network, currency: profile.currency },
1865
+ networkConfig: {
1866
+ networks: config.networks,
1867
+ types: {
1868
+ xPubType: config.types.xPubType,
1869
+ zPubType: config.types.zPubType,
1870
+ },
1871
+ },
1872
+ },
1873
+ });
1874
+ let node = hd.getRootNode();
1875
+ nodes.push(node);
1876
+ for (const child of [84, profile.slip44, request.accountIndex]) {
1877
+ node = node.deriveHardened(child);
1878
+ nodes.push(node);
1879
+ if (node.index !== child + 0x80000000)
1880
+ throw new bip84_native_plan_1.Bip84NativeError('ACCOUNT_MISMATCH');
1881
+ }
1882
+ const account = (0, public_bip84_1.parseBip84PublicAccount)({
1883
+ network: request.network.network,
1884
+ accountKey: node.neutered().toBase58(),
1885
+ });
1886
+ if (account.accountIndex !== request.accountIndex)
1887
+ throw new bip84_native_plan_1.Bip84NativeError('ACCOUNT_MISMATCH');
1888
+ const receive = (0, public_bip84_1.deriveBip84PublicAddress)({
1889
+ network: request.network.network,
1890
+ accountKey: account.canonicalKey,
1891
+ branch: 0,
1892
+ index: 0,
1893
+ });
1894
+ guard();
1895
+ return Object.freeze({
1896
+ type: 'HDAccount',
1897
+ accountIndex: request.accountIndex,
1898
+ account,
1899
+ receive,
1900
+ });
1901
+ }
1902
+ catch (error) {
1903
+ if (error instanceof bip84_native_plan_1.Bip84NativeError)
1904
+ throw error;
1905
+ throw new bip84_native_plan_1.Bip84NativeError('INVALID_REQUEST');
1906
+ }
1907
+ finally {
1908
+ // Only buffers owned by this operation; library/JS copies are not claimed zeroized.
1909
+ for (const node of nodes)
1910
+ node.privateKey?.fill(0);
1911
+ if (this.bip84Operation === operation)
1912
+ this.bip84Operation = undefined;
1913
+ }
1914
+ }
1915
+ /** Fixed selected-account native signing. No storage, discovery or broadcast. */
1916
+ async signPreparedSoftwareBip84Transaction(input, options) {
1917
+ if (this.bip84Operation)
1918
+ throw new bip84_native_plan_1.Bip84NativeError('BUSY');
1919
+ const operation = {};
1920
+ this.bip84Operation = operation;
1921
+ const nodes = [];
1922
+ try {
1923
+ const session = this.sessionPassword;
1924
+ const mnemonic = this.sessionMnemonic;
1925
+ const revision = this.privateRecordSessionRevision;
1926
+ const vault = this.getVault();
1927
+ const selected = { ...vault.activeAccount };
1928
+ const selectedAccount = vault.accounts[selected.type]?.[selected.id];
1929
+ const identity = {
1930
+ id: selectedAccount?.id,
1931
+ address: selectedAccount?.address,
1932
+ xpub: selectedAccount?.xpub,
1933
+ };
1934
+ const network = { ...vault.activeNetwork };
1935
+ const { account, validated } = (0, prepared_bip84_1.snapshotPreparedBip84Request)(input);
1936
+ const hostGuard = (0, bip84_native_plan_1.snapshotBip84Guard)(options);
1937
+ const profile = (0, bip84_native_plan_1.bip84Profile)(validated.prepared.plan.network.network);
1938
+ const assertCurrent = () => {
1939
+ const current = this.getVault();
1940
+ const active = current.accounts[selected.type]?.[selected.id];
1941
+ if (!session ||
1942
+ session.isCleared() ||
1943
+ this.sessionPassword !== session ||
1944
+ !mnemonic ||
1945
+ mnemonic.isCleared() ||
1946
+ this.sessionMnemonic !== mnemonic ||
1947
+ this.privateRecordSessionRevision !== revision ||
1948
+ this.bip84Operation !== operation ||
1949
+ selected.type !== types_1.KeyringAccountType.HDAccount ||
1950
+ selected.id !== account.index ||
1951
+ current.activeAccount.type !== selected.type ||
1952
+ current.activeAccount.id !== selected.id ||
1953
+ !active ||
1954
+ active.id !== account.index ||
1955
+ active.id !== identity.id ||
1956
+ active.address !== identity.address ||
1957
+ active.xpub !== identity.xpub ||
1958
+ active.isImported ||
1959
+ active.isLedgerWallet ||
1960
+ active.isTrezorWallet ||
1961
+ active.isSmartAccount ||
1962
+ network.kind !== sysweb3_network_1.INetworkType.Syscoin ||
1963
+ network.chainId !== profile.chainId ||
1964
+ network.slip44 !== profile.slip44 ||
1965
+ network.url !== validated.prepared.plan.network.endpoint ||
1966
+ current.activeNetwork.kind !== network.kind ||
1967
+ current.activeNetwork.chainId !== network.chainId ||
1968
+ current.activeNetwork.slip44 !== network.slip44 ||
1969
+ current.activeNetwork.url !== network.url)
1970
+ throw new bip84_native_plan_1.Bip84NativeError('CANCELLED');
1971
+ };
1972
+ const guard = () => {
1973
+ assertCurrent();
1974
+ hostGuard();
1975
+ assertCurrent();
1976
+ };
1977
+ guard();
1978
+ if (typeof identity.xpub !== 'string' ||
1979
+ (0, public_bip84_1.parseBip84PublicAccount)({
1980
+ network: validated.prepared.plan.network.network,
1981
+ accountKey: identity.xpub,
1982
+ }).canonicalKey !== account.expectedAccountKey)
1983
+ throw new bip84_native_plan_1.Bip84NativeError('ACCOUNT_MISMATCH');
1984
+ const words = this.getDecryptedMnemonic();
1985
+ if (!BIP84.validateMnemonic(words))
1986
+ throw new bip84_native_plan_1.Bip84NativeError('ACCOUNT_MISMATCH');
1987
+ guard();
1988
+ const config = (0, sysweb3_network_1.getNetworkConfig)(profile.slip44, profile.currency);
1989
+ const { hd } = (0, signers_1.getSyscoinSigners)({
1990
+ mnemonic: words,
1991
+ rpc: {
1992
+ formattedNetwork: { ...network, currency: profile.currency },
1993
+ networkConfig: {
1994
+ networks: config.networks,
1995
+ types: {
1996
+ xPubType: config.types.xPubType,
1997
+ zPubType: config.types.zPubType,
1998
+ },
1999
+ },
2000
+ },
2001
+ });
2002
+ let node = hd.getRootNode();
2003
+ nodes.push(node);
2004
+ for (const child of [84, profile.slip44, account.index]) {
2005
+ guard();
2006
+ node = node.deriveHardened(child);
2007
+ nodes.push(node);
2008
+ if (node.index !== child + 0x80000000)
2009
+ throw new bip84_native_plan_1.Bip84NativeError('ACCOUNT_MISMATCH');
2010
+ }
2011
+ guard();
2012
+ return (0, prepared_bip84_1.signValidatedBip84Plan)(validated, node, guard);
2013
+ }
2014
+ catch (error) {
2015
+ if (error instanceof bip84_native_plan_1.Bip84NativeError)
2016
+ throw error;
2017
+ throw new bip84_native_plan_1.Bip84NativeError('INVALID_REQUEST');
2018
+ }
2019
+ finally {
2020
+ (0, prepared_bip84_1.clearBip84PrivateNodes)(nodes);
2021
+ if (this.bip84Operation === operation)
2022
+ this.bip84Operation = undefined;
2023
+ }
2024
+ }
1799
2025
  /** Fixed-path derivation for an authenticated software host; no persistence. */
1800
2026
  async deriveSoftwareEvmHdRuntimeAccount(input, options) {
1801
2027
  // Capture the session and selection before inspecting caller-owned objects.