@chironbuilder/ootle-sdk 0.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/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/accountApi.d.ts +33 -0
- package/dist/accountApi.d.ts.map +1 -0
- package/dist/accountApi.js +1 -0
- package/dist/adapters.d.ts +13 -0
- package/dist/adapters.d.ts.map +1 -0
- package/dist/adapters.js +68 -0
- package/dist/componentAddress.d.ts +27 -0
- package/dist/componentAddress.d.ts.map +1 -0
- package/dist/componentAddress.js +68 -0
- package/dist/confidential.d.ts +50 -0
- package/dist/confidential.d.ts.map +1 -0
- package/dist/confidential.js +86 -0
- package/dist/derivation.d.ts +7 -0
- package/dist/derivation.d.ts.map +1 -0
- package/dist/derivation.js +47 -0
- package/dist/domainHash.d.ts +11 -0
- package/dist/domainHash.d.ts.map +1 -0
- package/dist/domainHash.js +44 -0
- package/dist/htlc.d.ts +26 -0
- package/dist/htlc.d.ts.map +1 -0
- package/dist/htlc.js +69 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/ootleNetwork.d.ts +4 -0
- package/dist/ootleNetwork.d.ts.map +1 -0
- package/dist/ootleNetwork.js +11 -0
- package/dist/ownershipProof.d.ts +4 -0
- package/dist/ownershipProof.d.ts.map +1 -0
- package/dist/ownershipProof.js +53 -0
- package/dist/storage.d.ts +61 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +110 -0
- package/dist/timeout.d.ts +2 -0
- package/dist/timeout.d.ts.map +1 -0
- package/dist/timeout.js +19 -0
- package/dist/vault.d.ts +12 -0
- package/dist/vault.d.ts.map +1 -0
- package/dist/vault.js +44 -0
- package/dist/wallet.d.ts +733 -0
- package/dist/wallet.d.ts.map +1 -0
- package/dist/wallet.js +1964 -0
- package/package.json +46 -0
- package/vendor/ootle-patched/README.md +67 -0
- package/vendor/ootle-patched/dist/index.d.ts +2129 -0
- package/vendor/ootle-patched/dist/index.js +1365 -0
- package/vendor/ootle-patched/package.json +23 -0
- package/vendor/ootle-wasm-patched/README.md +128 -0
- package/vendor/ootle-wasm-patched/ootle_wasm.d.ts +454 -0
- package/vendor/ootle-wasm-patched/ootle_wasm.js +9 -0
- package/vendor/ootle-wasm-patched/ootle_wasm_bg.js +1485 -0
- package/vendor/ootle-wasm-patched/ootle_wasm_bg.wasm +0 -0
- package/vendor/ootle-wasm-patched/ootle_wasm_bg.wasm.d.ts +83 -0
- package/vendor/ootle-wasm-patched/package.json +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 chironbuilds
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# @chironbuilder/ootle-sdk
|
|
2
|
+
|
|
3
|
+
A shared TypeScript client for [Tari Ootle](https://github.com/tari-project/tari-ootle) (L2): account
|
|
4
|
+
derivation, balances, transaction building/submission with automatic input resolution, HTLCs, and
|
|
5
|
+
stealth/confidential transfers.
|
|
6
|
+
|
|
7
|
+
This is the account core extracted from two independently-built wallets that had converged on
|
|
8
|
+
(mostly) the same code by hand — [Sapient](https://github.com/chironbuilds/tari-wallet), a Chrome
|
|
9
|
+
extension, and the [Tari L1 web wallet](https://universe.tari.mw)'s Ootle L2 support — after each had
|
|
10
|
+
picked up its own fixes the other was missing. This package exists so that stops happening: one
|
|
11
|
+
wallet core, used by both, with platform-specific concerns (storage, in this case) injected rather
|
|
12
|
+
than hardcoded.
|
|
13
|
+
|
|
14
|
+
**Status: early.** Extracted and reconciled in one pass; not yet wired back into either of the two
|
|
15
|
+
wallets above (they still carry their own local copies for now). Treat `0.x` as exactly that.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @chironbuilder/ootle-sdk
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This package vendors two small, still-unmerged patches: `@tari-project/ootle` (a `covenant_claims`
|
|
24
|
+
serialization gap in `StealthTransferStatement.toCompactJson()`, harmless for every transfer this SDK
|
|
25
|
+
builds today but not yet upstream-fixed) and `@tari-project/ootle-wasm` (adds
|
|
26
|
+
`createConfidentialWithdrawProofLiteral`, needed for confidential-vault withdrawals and not in the
|
|
27
|
+
published `0.39.x` builds). See `vendor/*/README.md` for each. Both are tracked for removal once
|
|
28
|
+
upstream ships the real fix — check there before assuming either is still needed.
|
|
29
|
+
|
|
30
|
+
## Storage
|
|
31
|
+
|
|
32
|
+
The SDK never touches a concrete storage API directly. It reads and writes through a small
|
|
33
|
+
`KeyValueStore` interface — configure one adapter, once, before calling anything else:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { configureOotleStorage } from "@chironbuilder/ootle-sdk";
|
|
37
|
+
import { chromeStorageAdapter } from "@chironbuilder/ootle-sdk/adapters";
|
|
38
|
+
// or: localStorageAdapter, inMemoryAdapter (tests)
|
|
39
|
+
|
|
40
|
+
configureOotleStorage(chromeStorageAdapter());
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Write your own adapter for any other host — it's three methods (`get`/`set`/`remove`).
|
|
44
|
+
|
|
45
|
+
This only covers what the account core itself needs (shielded outputs, pending shields, the
|
|
46
|
+
private-payment scan cursor, the known-substate-versions cache). Address books, connected-site
|
|
47
|
+
permissions, daemon connections, and a full transaction-history UI are application concerns with
|
|
48
|
+
very different shapes per host — keep those in your own app's storage layer.
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { OotleAccount, toOotleNetwork } from "@chironbuilder/ootle-sdk";
|
|
54
|
+
|
|
55
|
+
const account = OotleAccount.fromSeed(entropy, /* index */ 0, "esmeralda");
|
|
56
|
+
|
|
57
|
+
const balances = await account.getBalances();
|
|
58
|
+
await account.send(recipientWalletAddress, resourceAddress, amount);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
See `src/wallet.ts`'s doc comments for the full `OotleAccount` surface — `send`, `sendPrivately`,
|
|
62
|
+
`shield`/`unshield`, `htlcFund`/`htlcClaim`/`htlcRefund`, `claimTestnetXtr`, `execute` (the general
|
|
63
|
+
instruction-builder escape hatch, with automatic missing-input discovery and lock-contention retry).
|
|
64
|
+
|
|
65
|
+
## What's in here, and what isn't
|
|
66
|
+
|
|
67
|
+
- **Account core** (`wallet.ts`): `OotleAccount`, balance/plan-resolution helpers, the transaction
|
|
68
|
+
auto-resolve retry loop.
|
|
69
|
+
- **Crypto/derivation**: `derivation.ts`, `domainHash.ts`, `componentAddress.ts`, `vault.ts`,
|
|
70
|
+
`ownershipProof.ts`, `confidential.ts`.
|
|
71
|
+
- **HTLC**: `htlc.ts`.
|
|
72
|
+
- **Storage abstraction**: `storage.ts` + `adapters.ts`.
|
|
73
|
+
- **Not included**: UI, an approval/permission model for dApp connections, address books, daemon
|
|
74
|
+
(`tari_ootle_walletd`) relay support, transaction-history persistence. Those stay in each
|
|
75
|
+
consuming application.
|
|
76
|
+
|
|
77
|
+
## Developing
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm install
|
|
81
|
+
npm run typecheck
|
|
82
|
+
npm test
|
|
83
|
+
npm run build
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT — see [LICENSE](LICENSE). (The two wallets this was extracted from are licensed separately,
|
|
89
|
+
under PolyForm Noncommercial; this SDK is deliberately more permissive so other Ootle projects can
|
|
90
|
+
build on it without that restriction.)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { IndexerProvider } from "@tari-project/ootle-indexer";
|
|
2
|
+
import type { Instruction, SubstateRequirement } from "@tari-project/ootle-ts-bindings";
|
|
3
|
+
import type { TokenBalance } from "./wallet";
|
|
4
|
+
export interface TransactionExecuteOpts {
|
|
5
|
+
maxFee?: bigint;
|
|
6
|
+
dryRun?: boolean;
|
|
7
|
+
inputs?: SubstateRequirement[];
|
|
8
|
+
maxRetries?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Shared surface both `OotleAccount` (this extension's own seed-derived signer) and `DaemonAccount`
|
|
12
|
+
* (a relayed account on a connected `tari_ootle_walletd`) implement — the "hardware wallet" split:
|
|
13
|
+
* everywhere in background/index.ts that touches "the active account" is written against this
|
|
14
|
+
* interface, so it doesn't need to know or care which kind of account is actually active.
|
|
15
|
+
*/
|
|
16
|
+
export interface WalletAccountApi {
|
|
17
|
+
getComponentAddress(): Promise<string>;
|
|
18
|
+
getWalletAddress(): Promise<string>;
|
|
19
|
+
getBalances(): Promise<TokenBalance[]>;
|
|
20
|
+
/** The indexer connection this account's reads are served from — used for general-purpose
|
|
21
|
+
* substate lookups (`tari_getSubstate`) that aren't tied to any one account method. */
|
|
22
|
+
getProvider(): Promise<IndexerProvider>;
|
|
23
|
+
execute(instructions: Instruction[], opts?: TransactionExecuteOpts): Promise<unknown>;
|
|
24
|
+
/** `recipientAddress` accepts either the recipient's bech32m "otl_..." wallet address or their
|
|
25
|
+
* on-chain `component_...` address — both identify the same account, but only the former lets
|
|
26
|
+
* an implementation create the recipient's account on the fly if it doesn't exist yet (that
|
|
27
|
+
* requires the owner public key the wallet address encodes; a bare component address never
|
|
28
|
+
* reveals it, so that form can only pay an account that already exists). Prefer asking senders
|
|
29
|
+
* for the wallet address — it's the one form that always works. */
|
|
30
|
+
send(recipientAddress: string, resourceAddress: string, amount: bigint, maxFee?: bigint): Promise<unknown>;
|
|
31
|
+
claimTestnetXtr(): Promise<unknown>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=accountApi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accountApi.d.ts","sourceRoot":"","sources":["../src/accountApi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACxF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,WAAW,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IACvC;2FACuF;IACvF,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IACxC,OAAO,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,IAAI,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtF;;;;;uEAKmE;IACnE,IAAI,CAAC,gBAAgB,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3G,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACrC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { KeyValueStore } from "./storage";
|
|
2
|
+
/** For a Chrome (or any Manifest V3) extension. `chrome.storage.local` already stores structured
|
|
3
|
+
* values natively, so this is a thin wrapper, not a serializer. */
|
|
4
|
+
export declare function chromeStorageAdapter(area?: chrome.storage.StorageArea): KeyValueStore;
|
|
5
|
+
/** For a plain web page. `localStorage` only stores strings, so this serializes to/from JSON;
|
|
6
|
+
* unavailable or over quota (private browsing, storage disabled) degrades to "nothing was ever
|
|
7
|
+
* stored" rather than throwing, so a host can't be taken down by a browser storage restriction it
|
|
8
|
+
* doesn't control. */
|
|
9
|
+
export declare function localStorageAdapter(prefix?: string): KeyValueStore;
|
|
10
|
+
/** For tests, or a short-lived context with no real persistence -- state lives only as long as
|
|
11
|
+
* this object does. */
|
|
12
|
+
export declare function inMemoryAdapter(): KeyValueStore;
|
|
13
|
+
//# sourceMappingURL=adapters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C;mEACmE;AACnE,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,MAAM,CAAC,OAAO,CAAC,WAAkC,GAAG,aAAa,CAa3G;AAED;;;sBAGsB;AACtB,wBAAgB,mBAAmB,CAAC,MAAM,SAAK,GAAG,aAAa,CAyB9D;AAED;uBACuB;AACvB,wBAAgB,eAAe,IAAI,aAAa,CAa/C"}
|
package/dist/adapters.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Ready-made `KeyValueStore` adapters for the two hosts this SDK is known to run in. Pass one to
|
|
2
|
+
// `configureOotleStorage()` once at startup; write your own for any other host (see the interface
|
|
3
|
+
// in storage.ts -- it's three methods).
|
|
4
|
+
/** For a Chrome (or any Manifest V3) extension. `chrome.storage.local` already stores structured
|
|
5
|
+
* values natively, so this is a thin wrapper, not a serializer. */
|
|
6
|
+
export function chromeStorageAdapter(area = chrome.storage.local) {
|
|
7
|
+
return {
|
|
8
|
+
async get(key) {
|
|
9
|
+
const stored = await area.get(key);
|
|
10
|
+
return stored[key];
|
|
11
|
+
},
|
|
12
|
+
async set(key, value) {
|
|
13
|
+
await area.set({ [key]: value });
|
|
14
|
+
},
|
|
15
|
+
async remove(key) {
|
|
16
|
+
await area.remove(key);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** For a plain web page. `localStorage` only stores strings, so this serializes to/from JSON;
|
|
21
|
+
* unavailable or over quota (private browsing, storage disabled) degrades to "nothing was ever
|
|
22
|
+
* stored" rather than throwing, so a host can't be taken down by a browser storage restriction it
|
|
23
|
+
* doesn't control. */
|
|
24
|
+
export function localStorageAdapter(prefix = "") {
|
|
25
|
+
return {
|
|
26
|
+
async get(key) {
|
|
27
|
+
try {
|
|
28
|
+
const raw = localStorage.getItem(prefix + key);
|
|
29
|
+
return raw === null ? undefined : JSON.parse(raw);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
async set(key, value) {
|
|
36
|
+
try {
|
|
37
|
+
localStorage.setItem(prefix + key, JSON.stringify(value));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
/* quota or disabled storage -- the in-flight operation still completes in memory */
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
async remove(key) {
|
|
44
|
+
try {
|
|
45
|
+
localStorage.removeItem(prefix + key);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
/* nothing to do */
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** For tests, or a short-lived context with no real persistence -- state lives only as long as
|
|
54
|
+
* this object does. */
|
|
55
|
+
export function inMemoryAdapter() {
|
|
56
|
+
const map = new Map();
|
|
57
|
+
return {
|
|
58
|
+
async get(key) {
|
|
59
|
+
return map.get(key);
|
|
60
|
+
},
|
|
61
|
+
async set(key, value) {
|
|
62
|
+
map.set(key, value);
|
|
63
|
+
},
|
|
64
|
+
async remove(key) {
|
|
65
|
+
map.delete(key);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives an Ootle on-chain component address from a template address and an owner public key,
|
|
3
|
+
* reproducing `derive_component_address_from_public_key` (`crates/engine_types/src/component.rs`,
|
|
4
|
+
* tari-project/tari-ootle) byte-for-byte:
|
|
5
|
+
*
|
|
6
|
+
* Blake2b-256( domain_separation_tag("com.tari.ootle.engine", v0, "ComponentAddress")
|
|
7
|
+
* ‖ template_address (32 raw bytes, Borsh array encoding — no length prefix)
|
|
8
|
+
* ‖ borsh_len_u32_le(32) ‖ public_key (32 bytes) )
|
|
9
|
+
*
|
|
10
|
+
* The public key is Borsh-*slice*-encoded (`RistrettoPublicKeyBytes`'s manual `BorshSerialize` impl
|
|
11
|
+
* delegates to `self.as_bytes(): &[u8]`, which Borsh length-prefixes), unlike `template_address`
|
|
12
|
+
* (`Hash32([u8; 32])`, a fixed array — Borsh does not prefix fixed-size arrays). Verified against all
|
|
13
|
+
* three committed golden vectors in `tari-ootle`'s `crates/ootle_sdk_core/fixtures/address_derive/`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function deriveComponentAddress(templateAddress: Uint8Array, publicKey: Uint8Array): string;
|
|
16
|
+
/** The builtin Account template's address: 32 zero bytes (`ACCOUNT_TEMPLATE_ADDRESS` in `tari_template_builtin`). */
|
|
17
|
+
export declare const ACCOUNT_TEMPLATE_ADDRESS: Uint8Array<ArrayBuffer>;
|
|
18
|
+
export declare function deriveAccountComponentAddress(publicKey: Uint8Array): string;
|
|
19
|
+
/**
|
|
20
|
+
* Derives a recipient's on-chain account component address directly from their bech32m Ootle wallet
|
|
21
|
+
* address ("otl_..."). The wallet address already encodes the owner (spending) public key that
|
|
22
|
+
* `deriveAccountComponentAddress` derives the component address from -- the two identify the same
|
|
23
|
+
* account -- so callers (e.g. the Send flow) only need to collect one address from the user instead
|
|
24
|
+
* of also asking them to separately look up and paste their own `component_...` address.
|
|
25
|
+
*/
|
|
26
|
+
export declare function componentAddressFromWalletAddress(walletAddress: string): string;
|
|
27
|
+
//# sourceMappingURL=componentAddress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"componentAddress.d.ts","sourceRoot":"","sources":["../src/componentAddress.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,eAAe,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,GAAG,MAAM,CAcjG;AAED,qHAAqH;AACrH,eAAO,MAAM,wBAAwB,yBAAqB,CAAC;AAE3D,wBAAgB,6BAA6B,CAAC,SAAS,EAAE,UAAU,GAAG,MAAM,CAE3E;AAED;;;;;;GAMG;AACH,wBAAgB,iCAAiC,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAG/E"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { blake2b } from "@noble/hashes/blake2.js";
|
|
2
|
+
import { concatBytes, utf8ToBytes } from "@noble/hashes/utils.js";
|
|
3
|
+
import { parseOotleAddress } from "@tari-project/ootle-wasm";
|
|
4
|
+
/**
|
|
5
|
+
* Derives an Ootle on-chain component address from a template address and an owner public key,
|
|
6
|
+
* reproducing `derive_component_address_from_public_key` (`crates/engine_types/src/component.rs`,
|
|
7
|
+
* tari-project/tari-ootle) byte-for-byte:
|
|
8
|
+
*
|
|
9
|
+
* Blake2b-256( domain_separation_tag("com.tari.ootle.engine", v0, "ComponentAddress")
|
|
10
|
+
* ‖ template_address (32 raw bytes, Borsh array encoding — no length prefix)
|
|
11
|
+
* ‖ borsh_len_u32_le(32) ‖ public_key (32 bytes) )
|
|
12
|
+
*
|
|
13
|
+
* The public key is Borsh-*slice*-encoded (`RistrettoPublicKeyBytes`'s manual `BorshSerialize` impl
|
|
14
|
+
* delegates to `self.as_bytes(): &[u8]`, which Borsh length-prefixes), unlike `template_address`
|
|
15
|
+
* (`Hash32([u8; 32])`, a fixed array — Borsh does not prefix fixed-size arrays). Verified against all
|
|
16
|
+
* three committed golden vectors in `tari-ootle`'s `crates/ootle_sdk_core/fixtures/address_derive/`.
|
|
17
|
+
*/
|
|
18
|
+
export function deriveComponentAddress(templateAddress, publicKey) {
|
|
19
|
+
if (templateAddress.length !== 32)
|
|
20
|
+
throw new Error("templateAddress must be 32 bytes");
|
|
21
|
+
if (publicKey.length !== 32)
|
|
22
|
+
throw new Error("publicKey must be 32 bytes");
|
|
23
|
+
const domain = "com.tari.ootle.engine";
|
|
24
|
+
const version = "0";
|
|
25
|
+
const label = "ComponentAddress";
|
|
26
|
+
const tag = utf8ToBytes(`${domain}.v${version}.${label}`);
|
|
27
|
+
const digest = blake2b(concatBytes(u64le(tag.length), tag, templateAddress, u32le(publicKey.length), publicKey), { dkLen: 32 });
|
|
28
|
+
return `component_${bytesToHex(digest)}`;
|
|
29
|
+
}
|
|
30
|
+
/** The builtin Account template's address: 32 zero bytes (`ACCOUNT_TEMPLATE_ADDRESS` in `tari_template_builtin`). */
|
|
31
|
+
export const ACCOUNT_TEMPLATE_ADDRESS = new Uint8Array(32);
|
|
32
|
+
export function deriveAccountComponentAddress(publicKey) {
|
|
33
|
+
return deriveComponentAddress(ACCOUNT_TEMPLATE_ADDRESS, publicKey);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Derives a recipient's on-chain account component address directly from their bech32m Ootle wallet
|
|
37
|
+
* address ("otl_..."). The wallet address already encodes the owner (spending) public key that
|
|
38
|
+
* `deriveAccountComponentAddress` derives the component address from -- the two identify the same
|
|
39
|
+
* account -- so callers (e.g. the Send flow) only need to collect one address from the user instead
|
|
40
|
+
* of also asking them to separately look up and paste their own `component_...` address.
|
|
41
|
+
*/
|
|
42
|
+
export function componentAddressFromWalletAddress(walletAddress) {
|
|
43
|
+
const { owner_key } = parseOotleAddress(walletAddress);
|
|
44
|
+
return deriveAccountComponentAddress(owner_key);
|
|
45
|
+
}
|
|
46
|
+
function u32le(n) {
|
|
47
|
+
const b = new Uint8Array(4);
|
|
48
|
+
b[0] = n & 0xff;
|
|
49
|
+
b[1] = (n >>> 8) & 0xff;
|
|
50
|
+
b[2] = (n >>> 16) & 0xff;
|
|
51
|
+
b[3] = (n >>> 24) & 0xff;
|
|
52
|
+
return b;
|
|
53
|
+
}
|
|
54
|
+
function u64le(n) {
|
|
55
|
+
const b = new Uint8Array(8);
|
|
56
|
+
let v = BigInt(n);
|
|
57
|
+
for (let i = 0; i < 8; i++) {
|
|
58
|
+
b[i] = Number(v & 0xffn);
|
|
59
|
+
v >>= 8n;
|
|
60
|
+
}
|
|
61
|
+
return b;
|
|
62
|
+
}
|
|
63
|
+
function bytesToHex(bytes) {
|
|
64
|
+
let out = "";
|
|
65
|
+
for (const byte of bytes)
|
|
66
|
+
out += byte.toString(16).padStart(2, "0");
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type StealthCryptoProvider } from "@tari-project/ootle";
|
|
2
|
+
import type { OutputBody, TransactionEntry } from "@tari-project/ootle-ts-bindings";
|
|
3
|
+
export interface ConfidentialSumResult {
|
|
4
|
+
/** Total plaintext value recovered across every commitment this account could decrypt. */
|
|
5
|
+
total: bigint;
|
|
6
|
+
/** Commitments that failed to decrypt against this account's view key -- expected to be 0 for
|
|
7
|
+
* a vault's own commitments map (every entry there should be ours), so a nonzero count here is
|
|
8
|
+
* worth surfacing rather than silently swallowing. */
|
|
9
|
+
failedCount: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Sums the hidden value across every commitment in a vault's `Confidential.commitments` map.
|
|
13
|
+
* Each `OutputBody` is decrypted independently -- one failing (e.g. a commitment this account
|
|
14
|
+
* doesn't actually own) is counted rather than aborting the whole sum, so a single bad entry
|
|
15
|
+
* can't hide the rest of a real balance.
|
|
16
|
+
*/
|
|
17
|
+
export declare function sumConfidentialCommitments(crypto: StealthCryptoProvider, viewSecret: Uint8Array, commitments: Record<string, OutputBody>): Promise<ConfidentialSumResult>;
|
|
18
|
+
export interface ScannedStealthOutput {
|
|
19
|
+
resourceAddress: string;
|
|
20
|
+
/** 32-byte Pedersen commitment, hex. */
|
|
21
|
+
commitment: string;
|
|
22
|
+
amount: bigint;
|
|
23
|
+
transactionId: string;
|
|
24
|
+
/** The output's memo, if the sender attached one, as the raw JSON-encoded `Memo` union string
|
|
25
|
+
* `DecryptedData.memo` returns (see the SDK's own doc comment -- not plain text yet; callers use
|
|
26
|
+
* wallet.ts's `fromMemo()` to decode it). Decrypted alongside the amount, unlike
|
|
27
|
+
* `sumConfidentialCommitments`, which passes `skipMemo: true` since it only needs a running total. */
|
|
28
|
+
memo?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Walks a batch of `TransactionEntry`s (as returned by the indexer's `listRecentTransactions`)
|
|
32
|
+
* looking for `StealthTransfer` instructions, and tries to decrypt each output against this
|
|
33
|
+
* account's own view key -- the client-side half of the "scan new outputs" step described at
|
|
34
|
+
* ootle.tari.com/guides/stealth-resources, which nothing in the SDK wraps end-to-end today.
|
|
35
|
+
*
|
|
36
|
+
* A *pruned* transaction (what `listRecentTransactions` returns) only omits large blob payloads
|
|
37
|
+
* (template code, etc.) -- confirmed by reading the wire types, not assumed: instruction fields,
|
|
38
|
+
* including a `StealthTransfer`'s full `outputs_statement`, are preserved verbatim. Each output
|
|
39
|
+
* there carries its commitment, sender public nonce, and encrypted data as plain hex, which is
|
|
40
|
+
* everything `decryptInputData` needs -- no further per-output substate fetch required.
|
|
41
|
+
*
|
|
42
|
+
* Skips: commitments already in `knownCommitments` (no need to re-decrypt something already
|
|
43
|
+
* recorded), and any `StealthTransfer` whose resource address is a `Workspace` reference rather
|
|
44
|
+
* than a literal `Address` -- resolving a workspace reference would mean replaying that
|
|
45
|
+
* transaction's whole instruction sequence, out of scope for a lightweight scan. This wallet's own
|
|
46
|
+
* shield/unshield/sendPrivately always emit a literal `Address`, so this only ever skips
|
|
47
|
+
* unusually-built third-party transactions.
|
|
48
|
+
*/
|
|
49
|
+
export declare function scanTransactionsForOwnedOutputs(crypto: StealthCryptoProvider, viewSecret: Uint8Array, transactions: TransactionEntry[], knownCommitments: ReadonlySet<string>): Promise<ScannedStealthOutput[]>;
|
|
50
|
+
//# sourceMappingURL=confidential.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confidential.d.ts","sourceRoot":"","sources":["../src/confidential.ts"],"names":[],"mappings":"AAUA,OAAO,EAAoB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACnF,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAGpF,MAAM,WAAW,qBAAqB;IACpC,0FAA0F;IAC1F,KAAK,EAAE,MAAM,CAAC;IACd;;0DAEsD;IACtD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,qBAAqB,EAC7B,UAAU,EAAE,UAAU,EACtB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GACtC,OAAO,CAAC,qBAAqB,CAAC,CAgBhC;AAED,MAAM,WAAW,oBAAoB;IACnC,eAAe,EAAE,MAAM,CAAC;IACxB,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB;;;0GAGsG;IACtG,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,qBAAqB,EAC7B,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,gBAAgB,EAAE,EAChC,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,GACpC,OAAO,CAAC,oBAAoB,EAAE,CAAC,CA0BjC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Decrypting the private portion of a vault's Confidential resource container -- the value
|
|
2
|
+
// hidden inside Pedersen commitments (`ResourceContainer.Confidential.commitments`), readable
|
|
3
|
+
// only with the account's own view key. This is a *read*, not a spend: it only interprets data
|
|
4
|
+
// the caller already fetched (a vault substate), never touches the network or the account's
|
|
5
|
+
// owner key, and can never move funds.
|
|
6
|
+
//
|
|
7
|
+
// `decryptInputData` (not `decryptOwnedUtxo`) is the correct primitive here: `decryptOwnedUtxo`
|
|
8
|
+
// expects a full fetched `Utxo`-shaped substate (via `parseSubstateUtxo`), but a vault's inline
|
|
9
|
+
// `commitments` map holds raw `OutputBody` values directly, with no substate wrapper -- verified
|
|
10
|
+
// against `@tari-project/ootle`'s own type signatures before writing this, not assumed.
|
|
11
|
+
import { decryptInputData } from "@tari-project/ootle";
|
|
12
|
+
import { fromHex } from "./vault";
|
|
13
|
+
/**
|
|
14
|
+
* Sums the hidden value across every commitment in a vault's `Confidential.commitments` map.
|
|
15
|
+
* Each `OutputBody` is decrypted independently -- one failing (e.g. a commitment this account
|
|
16
|
+
* doesn't actually own) is counted rather than aborting the whole sum, so a single bad entry
|
|
17
|
+
* can't hide the rest of a real balance.
|
|
18
|
+
*/
|
|
19
|
+
export async function sumConfidentialCommitments(crypto, viewSecret, commitments) {
|
|
20
|
+
let total = 0n;
|
|
21
|
+
let failedCount = 0;
|
|
22
|
+
for (const [commitmentHex, body] of Object.entries(commitments)) {
|
|
23
|
+
try {
|
|
24
|
+
const { value } = await decryptInputData(crypto, fromHex(commitmentHex), fromHex(body.encrypted_data), {
|
|
25
|
+
senderPublicNonce: fromHex(body.public_nonce),
|
|
26
|
+
viewSecret,
|
|
27
|
+
skipMemo: true,
|
|
28
|
+
});
|
|
29
|
+
total += value;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
failedCount++;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { total, failedCount };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Walks a batch of `TransactionEntry`s (as returned by the indexer's `listRecentTransactions`)
|
|
39
|
+
* looking for `StealthTransfer` instructions, and tries to decrypt each output against this
|
|
40
|
+
* account's own view key -- the client-side half of the "scan new outputs" step described at
|
|
41
|
+
* ootle.tari.com/guides/stealth-resources, which nothing in the SDK wraps end-to-end today.
|
|
42
|
+
*
|
|
43
|
+
* A *pruned* transaction (what `listRecentTransactions` returns) only omits large blob payloads
|
|
44
|
+
* (template code, etc.) -- confirmed by reading the wire types, not assumed: instruction fields,
|
|
45
|
+
* including a `StealthTransfer`'s full `outputs_statement`, are preserved verbatim. Each output
|
|
46
|
+
* there carries its commitment, sender public nonce, and encrypted data as plain hex, which is
|
|
47
|
+
* everything `decryptInputData` needs -- no further per-output substate fetch required.
|
|
48
|
+
*
|
|
49
|
+
* Skips: commitments already in `knownCommitments` (no need to re-decrypt something already
|
|
50
|
+
* recorded), and any `StealthTransfer` whose resource address is a `Workspace` reference rather
|
|
51
|
+
* than a literal `Address` -- resolving a workspace reference would mean replaying that
|
|
52
|
+
* transaction's whole instruction sequence, out of scope for a lightweight scan. This wallet's own
|
|
53
|
+
* shield/unshield/sendPrivately always emit a literal `Address`, so this only ever skips
|
|
54
|
+
* unusually-built third-party transactions.
|
|
55
|
+
*/
|
|
56
|
+
export async function scanTransactionsForOwnedOutputs(crypto, viewSecret, transactions, knownCommitments) {
|
|
57
|
+
const found = [];
|
|
58
|
+
for (const entry of transactions) {
|
|
59
|
+
const tx = entry.transaction.V1.body.transaction;
|
|
60
|
+
for (const instruction of [...tx.instructions, ...tx.fee_instructions]) {
|
|
61
|
+
if (typeof instruction === "string" || !("StealthTransfer" in instruction))
|
|
62
|
+
continue;
|
|
63
|
+
const { resource_address_ref, statement } = instruction.StealthTransfer;
|
|
64
|
+
if (!("Address" in resource_address_ref))
|
|
65
|
+
continue;
|
|
66
|
+
const resourceAddress = resource_address_ref.Address;
|
|
67
|
+
for (const stealthOutput of statement.outputs_statement.outputs) {
|
|
68
|
+
const commitmentHex = stealthOutput.output.commitment;
|
|
69
|
+
if (knownCommitments.has(commitmentHex))
|
|
70
|
+
continue;
|
|
71
|
+
try {
|
|
72
|
+
const { value, memo } = await decryptInputData(crypto, fromHex(commitmentHex), fromHex(stealthOutput.output.encrypted_data), {
|
|
73
|
+
senderPublicNonce: fromHex(stealthOutput.output.sender_public_nonce),
|
|
74
|
+
viewSecret,
|
|
75
|
+
skipMemo: false,
|
|
76
|
+
});
|
|
77
|
+
found.push({ resourceAddress, commitment: commitmentHex, amount: value, transactionId: entry.transaction_id, memo });
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Not ours.
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return found;
|
|
86
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface DerivedAccountKeys {
|
|
2
|
+
ownerSecret: Uint8Array;
|
|
3
|
+
viewSecret: Uint8Array;
|
|
4
|
+
}
|
|
5
|
+
/** `entropy` is the 16-byte CipherSeed entropy (see cipherSeed.ts), not the raw seed/mnemonic. */
|
|
6
|
+
export declare function deriveAccountKeys(entropy: Uint8Array, index: number): DerivedAccountKeys;
|
|
7
|
+
//# sourceMappingURL=derivation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derivation.d.ts","sourceRoot":"","sources":["../src/derivation.ts"],"names":[],"mappings":"AA4CA,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,UAAU,CAAC;IACxB,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,kGAAkG;AAClG,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,kBAAkB,CAKxF"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// HD key derivation: CipherSeed entropy + account index -> a deterministic (ownerSecret,
|
|
2
|
+
// viewSecret) Ristretto scalar pair, matching the shape `SecretKeyWallet.fromSecretKey` expects
|
|
3
|
+
// (owner/spend key + view-only key — the "account" / "view_only_key" branches).
|
|
4
|
+
//
|
|
5
|
+
// Reproduces `derive_ristretto_key` (tari-ootle's `crates/wallet/crypto/src/derive.rs`)
|
|
6
|
+
// byte-for-byte: a domain-separated Blake2b-512 hash under `KeyManagerDomain`
|
|
7
|
+
// (`domainHash.ts`), label `"derive_key"`, chaining `entropy ‖ branch ‖ index (u64 LE)`
|
|
8
|
+
// — each individually length-prefixed by the hasher, not concatenated first — wide-reduced mod
|
|
9
|
+
// the Ristretto group order. Branch strings ("account" / "view_only_key") are
|
|
10
|
+
// `KeyBranch::as_str()` (tari-ootle's `crates/wallet/sdk/src/models/key.rs`).
|
|
11
|
+
import { utf8ToBytes } from "@noble/hashes/utils.js";
|
|
12
|
+
import { keyManagerDomainHasher } from "./domainHash";
|
|
13
|
+
// The Ristretto255 / Ed25519 group order l = 2^252 + 27742317777372353535851937790883648493.
|
|
14
|
+
const GROUP_ORDER = (1n << 252n) + 27742317777372353535851937790883648493n;
|
|
15
|
+
function u64le(n) {
|
|
16
|
+
const b = new Uint8Array(8);
|
|
17
|
+
let v = BigInt(n);
|
|
18
|
+
for (let i = 0; i < 8; i++) {
|
|
19
|
+
b[i] = Number(v & 0xffn);
|
|
20
|
+
v >>= 8n;
|
|
21
|
+
}
|
|
22
|
+
return b;
|
|
23
|
+
}
|
|
24
|
+
/** Reduce a 64-byte wide value mod the group order and re-encode as a 32-byte little-endian scalar. */
|
|
25
|
+
function scalarFromWideBytes(wide) {
|
|
26
|
+
let n = 0n;
|
|
27
|
+
for (let i = wide.length - 1; i >= 0; i--)
|
|
28
|
+
n = (n << 8n) | BigInt(wide[i] ?? 0);
|
|
29
|
+
n %= GROUP_ORDER;
|
|
30
|
+
const out = new Uint8Array(32);
|
|
31
|
+
for (let i = 0; i < 32; i++) {
|
|
32
|
+
out[i] = Number(n & 0xffn);
|
|
33
|
+
n >>= 8n;
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
function deriveScalar(entropy, branch, index) {
|
|
38
|
+
const wide = keyManagerDomainHasher("derive_key", 64).chain(entropy).chain(utf8ToBytes(branch)).chain(u64le(index)).finalize();
|
|
39
|
+
return scalarFromWideBytes(wide);
|
|
40
|
+
}
|
|
41
|
+
/** `entropy` is the 16-byte CipherSeed entropy (see cipherSeed.ts), not the raw seed/mnemonic. */
|
|
42
|
+
export function deriveAccountKeys(entropy, index) {
|
|
43
|
+
return {
|
|
44
|
+
ownerSecret: deriveScalar(entropy, "account", index),
|
|
45
|
+
viewSecret: deriveScalar(entropy, "view_only_key", index),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class DomainSeparatedHasher {
|
|
2
|
+
private chunks;
|
|
3
|
+
private readonly dkLen;
|
|
4
|
+
constructor(domain: string, version: number, label: string, dkLen: 32 | 64);
|
|
5
|
+
chain(data: Uint8Array): this;
|
|
6
|
+
finalize(): Uint8Array;
|
|
7
|
+
}
|
|
8
|
+
export declare const KEY_MANAGER_DOMAIN = "com.tari.base_layer.key_manager";
|
|
9
|
+
export declare const KEY_MANAGER_DOMAIN_VERSION = 1;
|
|
10
|
+
export declare function keyManagerDomainHasher(label: string, dkLen: 32 | 64): DomainSeparatedHasher;
|
|
11
|
+
//# sourceMappingURL=domainHash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domainHash.d.ts","sourceRoot":"","sources":["../src/domainHash.ts"],"names":[],"mappings":"AAyBA,qBAAa,qBAAqB;IAChC,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;gBAEpB,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE;IAM1E,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAK7B,QAAQ,IAAI,UAAU;CAGvB;AAED,eAAO,MAAM,kBAAkB,oCAAoC,CAAC;AACpE,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,GAAG,qBAAqB,CAE3F"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Reproduces `tari_crypto::hashing::DomainSeparatedHasher<Blake2b<N>, Domain>` byte-for-byte
|
|
2
|
+
// (tari-crypto v0.23.2 `src/hashing.rs`), used by both CipherSeed (cipherSeed.ts) and account-key
|
|
3
|
+
// derivation (derivation.ts), which both hash under `KeyManagerDomain` — `("com.tari.base_layer.key
|
|
4
|
+
// _manager", version 1)`, per `tari_hashing` v5.5.0 `hashing/src/domains.rs`.
|
|
5
|
+
//
|
|
6
|
+
// `new_with_label(label)` seeds the digest with `u64LE(tag.len()) ‖ tag`, where
|
|
7
|
+
// `tag = "{domain}.v{version}.{label}"`. Every subsequent `.chain(data)` call adds its OWN
|
|
8
|
+
// `u64LE(data.len()) ‖ data` — every chained field is individually length-prefixed, not just the
|
|
9
|
+
// initial tag. `DomainSeparatedHasher` is generic over the domain, so this class takes
|
|
10
|
+
// domain/version as constructor args too — domainHash.test.ts checks it against tari-crypto's own
|
|
11
|
+
// committed test vectors, which use a different domain ("com.tari.generic") than the one this
|
|
12
|
+
// codebase actually needs.
|
|
13
|
+
import { blake2b } from "@noble/hashes/blake2.js";
|
|
14
|
+
import { concatBytes, utf8ToBytes } from "@noble/hashes/utils.js";
|
|
15
|
+
function u64le(n) {
|
|
16
|
+
const b = new Uint8Array(8);
|
|
17
|
+
let v = BigInt(n);
|
|
18
|
+
for (let i = 0; i < 8; i++) {
|
|
19
|
+
b[i] = Number(v & 0xffn);
|
|
20
|
+
v >>= 8n;
|
|
21
|
+
}
|
|
22
|
+
return b;
|
|
23
|
+
}
|
|
24
|
+
export class DomainSeparatedHasher {
|
|
25
|
+
chunks;
|
|
26
|
+
dkLen;
|
|
27
|
+
constructor(domain, version, label, dkLen) {
|
|
28
|
+
this.dkLen = dkLen;
|
|
29
|
+
const tag = utf8ToBytes(label ? `${domain}.v${version}.${label}` : `${domain}.v${version}`);
|
|
30
|
+
this.chunks = [u64le(tag.length), tag];
|
|
31
|
+
}
|
|
32
|
+
chain(data) {
|
|
33
|
+
this.chunks.push(u64le(data.length), data);
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
finalize() {
|
|
37
|
+
return blake2b(concatBytes(...this.chunks), { dkLen: this.dkLen });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export const KEY_MANAGER_DOMAIN = "com.tari.base_layer.key_manager";
|
|
41
|
+
export const KEY_MANAGER_DOMAIN_VERSION = 1;
|
|
42
|
+
export function keyManagerDomainHasher(label, dkLen) {
|
|
43
|
+
return new DomainSeparatedHasher(KEY_MANAGER_DOMAIN, KEY_MANAGER_DOMAIN_VERSION, label, dkLen);
|
|
44
|
+
}
|
package/dist/htlc.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** An `AccessRule` satisfied only by a transaction signed with `publicKeyHex` (32 raw bytes, hex). */
|
|
2
|
+
export declare function accessRuleRequiringPublicKey(publicKeyHex: string): object;
|
|
3
|
+
export interface HtlcConditionsParams {
|
|
4
|
+
/** SHA-256 digest of the secret preimage, as 32 bytes hex. The party funding the HTLC never
|
|
5
|
+
* needs (and should never be given) the preimage itself -- only this hash. */
|
|
6
|
+
hashLockHex: string;
|
|
7
|
+
/** The epoch at/after which the refund path becomes admissible, and strictly before which the
|
|
8
|
+
* claim path is admissible -- so exactly one of the two leaves is ever satisfiable at a given
|
|
9
|
+
* epoch. Must fit a JS safe integer (real network epochs are nowhere near 2^53). */
|
|
10
|
+
refundEpoch: bigint;
|
|
11
|
+
/** The claimant's raw 32-byte owner public key, hex. */
|
|
12
|
+
claimantPublicKeyHex: string;
|
|
13
|
+
/** The refunder's (funder's own) raw 32-byte owner public key, hex. */
|
|
14
|
+
refunderPublicKeyHex: string;
|
|
15
|
+
}
|
|
16
|
+
/** The two-leaf HTLC condition tree, `[claimLeaf, refundLeaf]` -- pass directly as
|
|
17
|
+
* `createOutput({ payTo: { Conditions: htlcConditions(...) }, ... })`.
|
|
18
|
+
*
|
|
19
|
+
* Claim leaf: hashlock AND before the refund epoch AND the claimant's key.
|
|
20
|
+
* Refund leaf: at/after the refund epoch AND the refunder's key.
|
|
21
|
+
*
|
|
22
|
+
* The condition tree's root (computed on-chain from these leaves at output-creation time) is all
|
|
23
|
+
* that's committed publicly; these full leaves must be handed to the claimant out of band (e.g. by
|
|
24
|
+
* the swap protocol/dApp) so they can later reveal the claim leaf via `buildScriptPathWitness`. */
|
|
25
|
+
export declare function htlcConditions(params: HtlcConditionsParams): object[];
|
|
26
|
+
//# sourceMappingURL=htlc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"htlc.d.ts","sourceRoot":"","sources":["../src/htlc.ts"],"names":[],"mappings":"AAoBA,sGAAsG;AACtG,wBAAgB,4BAA4B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAazE;AAED,MAAM,WAAW,oBAAoB;IACnC;kFAC8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB;;wFAEoF;IACpF,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,oBAAoB,EAAE,MAAM,CAAC;IAC7B,uEAAuE;IACvE,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;mGAQmG;AACnG,wBAAgB,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,EAAE,CA0BrE"}
|