@chironbuilder/ootle-sdk 0.1.13 → 0.1.15
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/README.md +45 -1
- package/dist/burnClaim.d.ts +62 -20
- package/dist/burnClaim.d.ts.map +1 -1
- package/dist/burnClaim.js +201 -34
- package/dist/htlcSafety.d.ts +123 -0
- package/dist/htlcSafety.d.ts.map +1 -0
- package/dist/htlcSafety.js +275 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/storage.d.ts +92 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +85 -1
- package/dist/wallet.d.ts +201 -77
- package/dist/wallet.d.ts.map +1 -1
- package/dist/wallet.js +895 -275
- package/package.json +6 -2
- package/vendor/ootle-patched/dist/index.d.ts +57 -28
- package/vendor/ootle-patched/dist/index.js +1389 -1365
- package/vendor/ootle-patched/package.json +23 -23
- package/vendor/ootle-wasm-patched/ootle_wasm.d.ts +4 -4
- package/vendor/ootle-wasm-patched/ootle_wasm_bg.js +29 -17
- package/vendor/ootle-wasm-patched/ootle_wasm_bg.wasm +0 -0
- package/vendor/ootle-wasm-patched/ootle_wasm_bg.wasm.d.ts +4 -4
- package/vendor/ootle-wasm-patched/package.json +3 -4
package/README.md
CHANGED
|
@@ -81,13 +81,57 @@ The claimed funds arrive as a stealth output owned by this account and are recor
|
|
|
81
81
|
balance. Validators accept a burn only once its L1 block is well confirmed, so an early claim is
|
|
82
82
|
rejected and can be retried.
|
|
83
83
|
|
|
84
|
+
### HTLCs and atomic swaps
|
|
85
|
+
|
|
86
|
+
`htlcFund` / `htlcClaim` / `htlcRefund` are built for settlement safety, not just for constructing
|
|
87
|
+
the transactions:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
// Lock 5,000 units of a stealth-created asset from exact private outputs. The fee stays in its own
|
|
91
|
+
// native-TARI lane (here: private), the asset stays in the main lane, excess comes back as change.
|
|
92
|
+
const fund = await account.htlcFund(asset, 5_000n, claimantAddress, hashLockHex, refundEpoch, 50_000n,
|
|
93
|
+
{ kind: "private", feeResourceAddress: TARI }, { source: { kind: "stealth", commitments: [myUtxo] } });
|
|
94
|
+
// fund.conditions → send to the claimant. fund.outputMask is also journaled (fund.journalId).
|
|
95
|
+
|
|
96
|
+
// Claimant: nothing is revealed unless the funded output matches the agreed terms.
|
|
97
|
+
const check = await account.verifyHtlc(asset, fund.ownCommitment, fund.conditions, { amount: 5_000n, minEpochsBeforeRefund: 3n });
|
|
98
|
+
await account.htlcClaim(asset, fund.ownCommitment, fund.conditions, preimageHex, 50_000n, feeType,
|
|
99
|
+
{ expected: { amount: 5_000n, refunderPublicKeyHex, minEpochsBeforeRefund: 3n } });
|
|
100
|
+
|
|
101
|
+
// Funder, once currentEpoch >= refundEpoch:
|
|
102
|
+
await account.refundFromJournal(fund.journalId);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- **Two lanes.** The fee (native TARI, public or a private UTXO) never shares a UTXO with the HTLC
|
|
106
|
+
value. `source: { kind: "stealth" }` spends this account's own stealth outputs of the resource,
|
|
107
|
+
so no public balance of it is needed.
|
|
108
|
+
- **Dry run first.** Every fund/claim/refund dry-runs the exact transaction before the real submit
|
|
109
|
+
(`preflight: false` to skip — a claim's dry run sends the preimage to the indexer's dry-run
|
|
110
|
+
endpoint).
|
|
111
|
+
- **Verified before the preimage leaves the device.** `htlcClaim` (and `verifyHtlc`) checks the
|
|
112
|
+
amount, that the claim leaf is your key and the refund leaf the agreed counterparty's, that the
|
|
113
|
+
on-chain condition root equals the tree's root, that the preimage matches the hash lock, and that
|
|
114
|
+
enough claim epochs remain; it throws `HtlcVerificationError` otherwise.
|
|
115
|
+
- **Durable journal.** The sealed transaction, the condition tree and the HTLC output's mask (the
|
|
116
|
+
only thing that makes a refund possible) are persisted *before* submission. An ambiguous outcome
|
|
117
|
+
throws `HtlcUnknownOutcomeError` — "pending", never "failed" — and `reconcileHtlcs()` (call at
|
|
118
|
+
startup) resolves it by resubmitting the identical transaction and applying its bookkeeping.
|
|
119
|
+
`listHtlcs()` shows every operation and its state.
|
|
120
|
+
- **Exact inputs are reserved** for the duration of an operation (and while its outcome is
|
|
121
|
+
unknown), so a concurrent send/unshield/fee selection can't pick them.
|
|
122
|
+
- **Epoch rules match the engine:** the refund leaf is `AfterEpoch` (open when
|
|
123
|
+
`currentEpoch >= refundEpoch`), the claim leaf `BeforeEpoch` — see `isHtlcRefundable` /
|
|
124
|
+
`isHtlcClaimableByEpoch`.
|
|
125
|
+
- Claimed and refunded outputs are recorded in the shielded ledger, so they're spendable at once.
|
|
126
|
+
|
|
84
127
|
## What's in here, and what isn't
|
|
85
128
|
|
|
86
129
|
- **Account core** (`wallet.ts`): `OotleAccount`, balance/plan-resolution helpers, the transaction
|
|
87
130
|
auto-resolve retry loop.
|
|
88
131
|
- **Crypto/derivation**: `derivation.ts`, `domainHash.ts`, `componentAddress.ts`, `vault.ts`,
|
|
89
132
|
`ownershipProof.ts`, `confidential.ts`.
|
|
90
|
-
- **HTLC**: `htlc.ts
|
|
133
|
+
- **HTLC**: `htlc.ts` (condition trees), `htlcSafety.ts` (epoch rules, pre-claim verification,
|
|
134
|
+
per-output masks, outcome classification) + the journaled `OotleAccount` HTLC methods.
|
|
91
135
|
- **L1 burn claims**: `burnClaim.ts` (proof assembly) + `OotleAccount.claimBurn`.
|
|
92
136
|
- **Storage abstraction**: `storage.ts` + `adapters.ts`.
|
|
93
137
|
- **Not included**: UI, an approval/permission model for dApp connections, address books, daemon
|
package/dist/burnClaim.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ export interface BurnClaimProofContents {
|
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* The claim material an L1 wallet has as soon as it builds a burn (see tari_l1_wasm's
|
|
10
|
-
* `WasmSignedBurn`). Everything except the
|
|
11
|
-
* is mined.
|
|
10
|
+
* `WasmSignedBurn`). Everything except the output's inclusion proof, which only exists once the
|
|
11
|
+
* burn is mined.
|
|
12
12
|
*/
|
|
13
13
|
export interface L1BurnProofParts {
|
|
14
14
|
/** The Ootle account public key `P` the burn was addressed to. */
|
|
@@ -19,29 +19,71 @@ export interface L1BurnProofParts {
|
|
|
19
19
|
senderOffsetPublicKeyHex: string;
|
|
20
20
|
encryptedDataHex: string;
|
|
21
21
|
amount: bigint;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
fee: bigint;
|
|
25
|
-
lockHeight: bigint;
|
|
26
|
-
excessHex: string;
|
|
27
|
-
nonceHex: string;
|
|
28
|
-
signatureHex: string;
|
|
29
|
-
};
|
|
22
|
+
/** No longer part of a claim (tari-ootle#2709); kept so records written before it still load. */
|
|
23
|
+
kernel?: unknown;
|
|
30
24
|
}
|
|
31
|
-
/**
|
|
32
|
-
export interface
|
|
33
|
-
/** base64 */
|
|
34
|
-
block_hash: string;
|
|
35
|
-
/** base64 */
|
|
36
|
-
encoded_merkle_proof: string;
|
|
25
|
+
/** An L1 MMR inclusion proof, as a base node serialises it (hashes are hex). */
|
|
26
|
+
export interface L1MmrInclusionProof {
|
|
37
27
|
leaf_index: number | string;
|
|
38
|
-
|
|
28
|
+
mmr_size: number | string;
|
|
29
|
+
path: string[];
|
|
30
|
+
peaks: string[];
|
|
39
31
|
}
|
|
40
|
-
/**
|
|
41
|
-
|
|
32
|
+
/**
|
|
33
|
+
* A base node's `BurnOutputProof` (the `proof` of a `/generate_burn_output_proof` response, or
|
|
34
|
+
* `claim_proof.output_proof` of a `minotari_console_wallet` proof file). Byte fields are hex.
|
|
35
|
+
*/
|
|
36
|
+
export interface BurnOutputProof {
|
|
37
|
+
block_hash: string;
|
|
38
|
+
block_height?: number | string;
|
|
39
|
+
output: {
|
|
40
|
+
version: number;
|
|
41
|
+
/** Consensus (borsh) encoded `OutputFeatures` */
|
|
42
|
+
features: string;
|
|
43
|
+
commitment: string;
|
|
44
|
+
rangeproof_hash: string;
|
|
45
|
+
script: string;
|
|
46
|
+
sender_offset_public_key: string;
|
|
47
|
+
metadata_signature: {
|
|
48
|
+
ephemeral_commitment: string;
|
|
49
|
+
ephemeral_pubkey: string;
|
|
50
|
+
u_a: string;
|
|
51
|
+
u_x: string;
|
|
52
|
+
u_y: string;
|
|
53
|
+
};
|
|
54
|
+
covenant: string;
|
|
55
|
+
encrypted_data: string;
|
|
56
|
+
minimum_value_promise: number | string;
|
|
57
|
+
};
|
|
58
|
+
normal_output_proof: L1MmrInclusionProof;
|
|
59
|
+
normal_output_mr: string;
|
|
60
|
+
block_output_proof: L1MmrInclusionProof;
|
|
61
|
+
}
|
|
62
|
+
type BurnOutputFeatures = MinotariBurnClaimProof["output"]["features"];
|
|
63
|
+
/**
|
|
64
|
+
* Decodes a burn output's consensus-encoded `OutputFeatures` into the fields a claim carries,
|
|
65
|
+
* refusing anything Ootle can't mint against (not a burn, or no confidential-output sidechain
|
|
66
|
+
* feature naming a claimant) -- the same checks as `claim_proof_from_l1`.
|
|
67
|
+
*/
|
|
68
|
+
export declare function decodeBurnOutputFeatures(featuresHex: string): BurnOutputFeatures;
|
|
69
|
+
/**
|
|
70
|
+
* Converts a base node's `BurnOutputProof` plus the burn's ownership proof and value into the
|
|
71
|
+
* claim Ootle verifies (`claim_proof_from_l1`).
|
|
72
|
+
*/
|
|
73
|
+
export declare function claimProofFromL1(outputProof: BurnOutputProof, ownershipProof: {
|
|
74
|
+
public_nonce: string;
|
|
75
|
+
signature: string;
|
|
76
|
+
}, value: bigint | string | number): MinotariBurnClaimProof;
|
|
77
|
+
/**
|
|
78
|
+
* Joins an L1 burn's claim material with its mined output's inclusion proof (a base node's
|
|
79
|
+
* `/generate_burn_output_proof` `proof`) into a claimable proof.
|
|
80
|
+
*/
|
|
81
|
+
export declare function assembleBurnClaimProof(parts: L1BurnProofParts, outputProof: BurnOutputProof): BurnClaimProofContents;
|
|
42
82
|
/**
|
|
43
83
|
* Reads a proof file written by `minotari_console_wallet` into its `burn_proofs` directory
|
|
44
|
-
* (`CompleteClaimBurnProof`
|
|
84
|
+
* (`CompleteClaimBurnProof`: `claim_proof` is a Minotari `BurnClaimProof` carrying the
|
|
85
|
+
* `output_proof`), converting it to Ootle's claim. A file already in Ootle's shape passes through.
|
|
45
86
|
*/
|
|
46
87
|
export declare function parseConsoleWalletBurnProof(json: string | Record<string, unknown>): BurnClaimProofContents;
|
|
88
|
+
export {};
|
|
47
89
|
//# sourceMappingURL=burnClaim.d.ts.map
|
package/dist/burnClaim.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"burnClaim.d.ts","sourceRoot":"","sources":["../src/burnClaim.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"burnClaim.d.ts","sourceRoot":"","sources":["../src/burnClaim.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAE9E,gGAAgG;AAChG,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,sBAAsB,CAAC;IACpC,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kEAAkE;IAClE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,wBAAwB,EAAE,MAAM,CAAC;IACjC,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,iGAAiG;IACjG,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,gFAAgF;AAChF,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,iDAAiD;QACjD,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,eAAe,EAAE,MAAM,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC;QACf,wBAAwB,EAAE,MAAM,CAAC;QACjC,kBAAkB,EAAE;YAClB,oBAAoB,EAAE,MAAM,CAAC;YAC7B,gBAAgB,EAAE,MAAM,CAAC;YACzB,GAAG,EAAE,MAAM,CAAC;YACZ,GAAG,EAAE,MAAM,CAAC;YACZ,GAAG,EAAE,MAAM,CAAC;SACb,CAAC;QACF,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;QACvB,qBAAqB,EAAE,MAAM,GAAG,MAAM,CAAC;KACxC,CAAC;IACF,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,mBAAmB,CAAC;CACzC;AAgFD,KAAK,kBAAkB,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAC,UAAU,CAAC,CAAC;AAEvE;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB,CA6BhF;AA6BD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,eAAe,EAC5B,cAAc,EAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,EAC3D,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAC9B,sBAAsB,CA2BxB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,eAAe,GAAG,sBAAsB,CAWpH;AASD;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,sBAAsB,CA2B1G"}
|
package/dist/burnClaim.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
// Claim proofs for Layer 1 (minotari) burns, in the exact serde shape Ootle's `ClaimBurn`
|
|
2
|
-
// instruction deserialises (`tari_engine_types::confidential::MinotariBurnClaimProof`)
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// instruction deserialises (`tari_engine_types::confidential::MinotariBurnClaimProof`).
|
|
3
|
+
//
|
|
4
|
+
// Since tari-ootle#2709 (Ootle 0.42, Minotari 6.0.1-pre.1) a claim proves the L1 burn *output*
|
|
5
|
+
// rather than its kernel: the output's hashed fields plus a two-level MMR inclusion proof (output
|
|
6
|
+
// hash -> normal output MMR root -> last leaf of the block's `block_output_mr`). A base node serves
|
|
7
|
+
// that proof as a Minotari `BurnOutputProof` (`/generate_burn_output_proof?commitment=<hex>`); this
|
|
8
|
+
// module converts it the way `claim_proof_from_l1` (tari_ootle_app_utilities) does, and
|
|
9
|
+
// `burnClaim.test.ts` checks the conversion against vectors produced by that Rust function.
|
|
5
10
|
const HEX_32 = /^[0-9a-f]{64}$/;
|
|
6
11
|
function hex32(value, field) {
|
|
7
12
|
const v = value.toLowerCase();
|
|
@@ -9,40 +14,192 @@ function hex32(value, field) {
|
|
|
9
14
|
throw new Error(`${field} must be 32 bytes of hex`);
|
|
10
15
|
return v;
|
|
11
16
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
function hexToBytes(hex, field) {
|
|
18
|
+
if (!/^[0-9a-f]*$/i.test(hex) || hex.length % 2 !== 0)
|
|
19
|
+
throw new Error(`${field} must be hex`);
|
|
20
|
+
const out = new Uint8Array(hex.length / 2);
|
|
21
|
+
for (let i = 0; i < out.length; i++)
|
|
22
|
+
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
function bytesToHex(bytes) {
|
|
26
|
+
let out = "";
|
|
27
|
+
for (const b of bytes)
|
|
28
|
+
out += b.toString(16).padStart(2, "0");
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
function bytesToBase64(bytes) {
|
|
32
|
+
let bin = "";
|
|
33
|
+
for (const b of bytes)
|
|
34
|
+
bin += String.fromCharCode(b);
|
|
35
|
+
return btoa(bin);
|
|
36
|
+
}
|
|
37
|
+
/** Reads the L1 consensus (borsh) encoding. Every read is bounds-checked. */
|
|
38
|
+
class BorshReader {
|
|
39
|
+
buf;
|
|
40
|
+
what;
|
|
41
|
+
pos = 0;
|
|
42
|
+
constructor(buf, what) {
|
|
43
|
+
this.buf = buf;
|
|
44
|
+
this.what = what;
|
|
45
|
+
}
|
|
46
|
+
take(n) {
|
|
47
|
+
if (this.pos + n > this.buf.length)
|
|
48
|
+
throw new Error(`${this.what}: truncated`);
|
|
49
|
+
const out = this.buf.subarray(this.pos, this.pos + n);
|
|
50
|
+
this.pos += n;
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
u8() {
|
|
54
|
+
return this.take(1)[0];
|
|
55
|
+
}
|
|
56
|
+
u32() {
|
|
57
|
+
const b = this.take(4);
|
|
58
|
+
return (b[0] | (b[1] << 8) | (b[2] << 16)) + b[3] * 0x1000000;
|
|
59
|
+
}
|
|
60
|
+
u64() {
|
|
61
|
+
const b = this.take(8);
|
|
62
|
+
let v = 0n;
|
|
63
|
+
for (let i = 7; i >= 0; i--)
|
|
64
|
+
v = (v << 8n) | BigInt(b[i]);
|
|
65
|
+
return v;
|
|
66
|
+
}
|
|
67
|
+
/** A `Vec<u8>` / `&[u8]`: u32 length, then the bytes. */
|
|
68
|
+
bytes(max) {
|
|
69
|
+
const n = this.u32();
|
|
70
|
+
if (n > max)
|
|
71
|
+
throw new Error(`${this.what}: byte string of ${n} exceeds ${max}`);
|
|
72
|
+
return this.take(n);
|
|
73
|
+
}
|
|
74
|
+
/** A compressed key or scalar, which L1 encodes as a 32-byte `&[u8]`. */
|
|
75
|
+
key(field) {
|
|
76
|
+
const b = this.bytes(32);
|
|
77
|
+
if (b.length !== 32)
|
|
78
|
+
throw new Error(`${this.what}: ${field} is ${b.length} bytes, expected 32`);
|
|
79
|
+
return bytesToHex(b);
|
|
80
|
+
}
|
|
81
|
+
option() {
|
|
82
|
+
const tag = this.u8();
|
|
83
|
+
if (tag > 1)
|
|
84
|
+
throw new Error(`${this.what}: invalid Option tag ${tag}`);
|
|
85
|
+
return tag === 1;
|
|
86
|
+
}
|
|
87
|
+
end() {
|
|
88
|
+
if (this.pos !== this.buf.length)
|
|
89
|
+
throw new Error(`${this.what}: ${this.buf.length - this.pos} trailing bytes`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const OUTPUT_TYPE_BURN = 2;
|
|
93
|
+
/** `SideChainFeatureData::ConfidentialOutput` */
|
|
94
|
+
const SIDECHAIN_CONFIDENTIAL_OUTPUT = 2;
|
|
95
|
+
/** `CoinBaseExtra = MaxSizeBytes<258>` */
|
|
96
|
+
const COINBASE_EXTRA_MAX = 258;
|
|
97
|
+
/**
|
|
98
|
+
* Decodes a burn output's consensus-encoded `OutputFeatures` into the fields a claim carries,
|
|
99
|
+
* refusing anything Ootle can't mint against (not a burn, or no confidential-output sidechain
|
|
100
|
+
* feature naming a claimant) -- the same checks as `claim_proof_from_l1`.
|
|
101
|
+
*/
|
|
102
|
+
export function decodeBurnOutputFeatures(featuresHex) {
|
|
103
|
+
const r = new BorshReader(hexToBytes(featuresHex, "burn output features"), "burn output features");
|
|
104
|
+
const version = r.u8();
|
|
105
|
+
const outputType = r.u8();
|
|
106
|
+
const maturity = r.u64();
|
|
107
|
+
r.bytes(COINBASE_EXTRA_MAX);
|
|
108
|
+
if (outputType !== OUTPUT_TYPE_BURN)
|
|
109
|
+
throw new Error(`the output is not a burn (output type ${outputType})`);
|
|
110
|
+
if (!r.option())
|
|
111
|
+
throw new Error("the burn output has no sidechain feature, so it names no claimant");
|
|
112
|
+
const variant = r.u8();
|
|
113
|
+
if (variant !== SIDECHAIN_CONFIDENTIAL_OUTPUT) {
|
|
114
|
+
throw new Error(`the burn output's sidechain feature is not a confidential output (variant ${variant})`);
|
|
115
|
+
}
|
|
116
|
+
const claimPublicKey = r.key("claim_public_key");
|
|
117
|
+
let sidechainId = null;
|
|
118
|
+
if (r.option()) {
|
|
119
|
+
const publicKey = r.key("sidechain public_key");
|
|
120
|
+
const nonce = r.key("sidechain knowledge_proof.public_nonce");
|
|
121
|
+
const signature = r.key("sidechain knowledge_proof.signature");
|
|
122
|
+
sidechainId = { public_key: publicKey, knowledge_proof: { public_nonce: nonce, signature } };
|
|
16
123
|
}
|
|
124
|
+
const rangeProofType = r.u8();
|
|
125
|
+
r.end();
|
|
17
126
|
return {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
127
|
+
version,
|
|
128
|
+
maturity: maturity.toString(),
|
|
129
|
+
claim_public_key: claimPublicKey,
|
|
130
|
+
sidechain_id: sidechainId,
|
|
131
|
+
range_proof_type: rangeProofType,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** The L1 consensus (borsh) encoding of a `ComAndPubSignature`: five 32-byte fields, each length-prefixed. */
|
|
135
|
+
function encodeMetadataSignature(sig) {
|
|
136
|
+
const fields = [sig.ephemeral_commitment, sig.ephemeral_pubkey, sig.u_a, sig.u_x, sig.u_y];
|
|
137
|
+
const out = new Uint8Array(fields.length * 36);
|
|
138
|
+
fields.forEach((f, i) => {
|
|
139
|
+
out.set([32, 0, 0, 0], i * 36);
|
|
140
|
+
out.set(hexToBytes(hex32(f, "metadata_signature field"), "metadata_signature field"), i * 36 + 4);
|
|
141
|
+
});
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
function mmrProof(p) {
|
|
145
|
+
return {
|
|
146
|
+
leaf_index: String(p.leaf_index),
|
|
147
|
+
mmr_size: String(p.mmr_size),
|
|
148
|
+
path: p.path.map((h) => hex32(h, "MMR path hash")),
|
|
149
|
+
peaks: p.peaks.map((h) => hex32(h, "MMR peak hash")),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/** A `BoundedVec<u8, 1, N>` field: non-empty, at most `max` bytes, serialised as base64. */
|
|
153
|
+
function opaque(hex, field, max) {
|
|
154
|
+
const b = hexToBytes(hex, field);
|
|
155
|
+
if (b.length < 1 || b.length > max)
|
|
156
|
+
throw new Error(`burn output ${field} length ${b.length} is out of range`);
|
|
157
|
+
return bytesToBase64(b);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Converts a base node's `BurnOutputProof` plus the burn's ownership proof and value into the
|
|
161
|
+
* claim Ootle verifies (`claim_proof_from_l1`).
|
|
162
|
+
*/
|
|
163
|
+
export function claimProofFromL1(outputProof, ownershipProof, value) {
|
|
164
|
+
const o = outputProof.output;
|
|
165
|
+
return {
|
|
166
|
+
commitment: hex32(o.commitment, "commitment"),
|
|
167
|
+
ownership_proof: {
|
|
168
|
+
public_nonce: hex32(ownershipProof.public_nonce, "ownership_proof.public_nonce"),
|
|
169
|
+
signature: hex32(ownershipProof.signature, "ownership_proof.signature"),
|
|
170
|
+
},
|
|
171
|
+
value: BigInt(value).toString(),
|
|
172
|
+
output: {
|
|
173
|
+
version: o.version,
|
|
174
|
+
features: decodeBurnOutputFeatures(o.features),
|
|
175
|
+
rangeproof_hash: hex32(o.rangeproof_hash, "rangeproof_hash"),
|
|
176
|
+
script: opaque(o.script, "script", 4096),
|
|
177
|
+
sender_offset_public_key: hex32(o.sender_offset_public_key, "sender_offset_public_key"),
|
|
178
|
+
metadata_signature: bytesToBase64(encodeMetadataSignature(o.metadata_signature)),
|
|
179
|
+
covenant: opaque(o.covenant, "covenant", 4096),
|
|
180
|
+
encrypted_data: opaque(o.encrypted_data, "encrypted data", 4096),
|
|
181
|
+
minimum_value_promise: BigInt(o.minimum_value_promise).toString(),
|
|
182
|
+
},
|
|
183
|
+
inclusion_proof: {
|
|
184
|
+
block_hash: hex32(outputProof.block_hash, "block_hash"),
|
|
185
|
+
normal_output_proof: mmrProof(outputProof.normal_output_proof),
|
|
186
|
+
normal_output_mr: hex32(outputProof.normal_output_mr, "normal_output_mr"),
|
|
187
|
+
block_output_proof: mmrProof(outputProof.block_output_proof),
|
|
42
188
|
},
|
|
43
|
-
encrypted_data: parts.encryptedDataHex.toLowerCase(),
|
|
44
189
|
};
|
|
45
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Joins an L1 burn's claim material with its mined output's inclusion proof (a base node's
|
|
193
|
+
* `/generate_burn_output_proof` `proof`) into a claimable proof.
|
|
194
|
+
*/
|
|
195
|
+
export function assembleBurnClaimProof(parts, outputProof) {
|
|
196
|
+
const commitment = hex32(parts.commitmentHex, "commitmentHex");
|
|
197
|
+
if (outputProof.output.commitment.toLowerCase() !== commitment) {
|
|
198
|
+
throw new Error("The base node's burn output proof is for a different commitment than this burn");
|
|
199
|
+
}
|
|
200
|
+
const claim = claimProofFromL1(outputProof, { public_nonce: parts.ownershipNonceHex, signature: parts.ownershipSignatureHex }, parts.amount);
|
|
201
|
+
return { claim_proof: claim, encrypted_data: bytesToHex(hexToBytes(parts.encryptedDataHex, "encryptedDataHex")) };
|
|
202
|
+
}
|
|
46
203
|
function base64ToHex(b64) {
|
|
47
204
|
const bin = atob(b64);
|
|
48
205
|
let out = "";
|
|
@@ -52,7 +209,8 @@ function base64ToHex(b64) {
|
|
|
52
209
|
}
|
|
53
210
|
/**
|
|
54
211
|
* Reads a proof file written by `minotari_console_wallet` into its `burn_proofs` directory
|
|
55
|
-
* (`CompleteClaimBurnProof`
|
|
212
|
+
* (`CompleteClaimBurnProof`: `claim_proof` is a Minotari `BurnClaimProof` carrying the
|
|
213
|
+
* `output_proof`), converting it to Ootle's claim. A file already in Ootle's shape passes through.
|
|
56
214
|
*/
|
|
57
215
|
export function parseConsoleWalletBurnProof(json) {
|
|
58
216
|
const file = (typeof json === "string" ? JSON.parse(json) : json);
|
|
@@ -64,5 +222,14 @@ export function parseConsoleWalletBurnProof(json) {
|
|
|
64
222
|
: /^[0-9a-f]+$/i.test(file.encrypted_data) && file.encrypted_data.length % 2 === 0
|
|
65
223
|
? file.encrypted_data.toLowerCase()
|
|
66
224
|
: base64ToHex(file.encrypted_data);
|
|
67
|
-
|
|
225
|
+
const cp = file.claim_proof;
|
|
226
|
+
if (cp.output_proof) {
|
|
227
|
+
if (!cp.ownership_proof || cp.value === undefined)
|
|
228
|
+
throw new Error("Burn proof is missing its ownership proof or value");
|
|
229
|
+
return { claim_proof: claimProofFromL1(cp.output_proof, cp.ownership_proof, cp.value), encrypted_data: encrypted };
|
|
230
|
+
}
|
|
231
|
+
if (cp.inclusion_proof && cp.output)
|
|
232
|
+
return { claim_proof: cp, encrypted_data: encrypted };
|
|
233
|
+
throw new Error("This burn proof predates Ootle 0.42 (it proves the burn kernel, not the burn output). Regenerate it with a " +
|
|
234
|
+
"Minotari 6.0.1+ wallet, or claim the burn from its commitment.");
|
|
68
235
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/** The refund leaf is `AfterEpoch(refundEpoch)`: admissible when `currentEpoch >= refundEpoch`. */
|
|
2
|
+
export declare function isHtlcRefundable(currentEpoch: bigint, refundEpoch: bigint): boolean;
|
|
3
|
+
/** The claim leaf is `BeforeEpoch(refundEpoch)`: admissible only while `currentEpoch < refundEpoch`. */
|
|
4
|
+
export declare function isHtlcClaimableByEpoch(currentEpoch: bigint, refundEpoch: bigint): boolean;
|
|
5
|
+
/** Epochs left in which a claim is still admissible (0 once the claim window has closed). */
|
|
6
|
+
export declare function epochsUntilRefund(currentEpoch: bigint, refundEpoch: bigint): bigint;
|
|
7
|
+
/**
|
|
8
|
+
* The on-chain condition root of a `[claim, refund]` tree — what a `PayTo::Conditions` output
|
|
9
|
+
* commits to (its `auth.Script`). Computed through the refund leaf, which consumes no witness data,
|
|
10
|
+
* so no preimage is needed.
|
|
11
|
+
*/
|
|
12
|
+
export declare function conditionRootOf(conditions: object[]): string;
|
|
13
|
+
/**
|
|
14
|
+
* Finds the script-path condition root committed by an on-chain UTXO body — the `Script` variant of
|
|
15
|
+
* its spend authorization. Searched structurally (a `Script` key holding 32 bytes of hex) rather
|
|
16
|
+
* than by a fixed path, so an indexer-side field rename can't turn "is this our HTLC?" into a
|
|
17
|
+
* silent pass: a body with no such field returns `null`, which every caller treats as a mismatch.
|
|
18
|
+
*/
|
|
19
|
+
export declare function findScriptRoot(body: unknown, depth?: number): string | null;
|
|
20
|
+
/** What the claimant's side of the swap agreed to — checked against the chain before the preimage
|
|
21
|
+
* is ever revealed. Omit a field to skip that particular check. */
|
|
22
|
+
export interface HtlcExpectations {
|
|
23
|
+
/** Exact amount expected (raw units). */
|
|
24
|
+
amount?: bigint;
|
|
25
|
+
/** Minimum acceptable amount, when an exact figure isn't agreed. */
|
|
26
|
+
minAmount?: bigint;
|
|
27
|
+
hashLockHex?: string;
|
|
28
|
+
refundEpoch?: bigint;
|
|
29
|
+
/** The counterparty (funder) who may refund — checked by rebuilding the tree from expectations. */
|
|
30
|
+
refunderPublicKeyHex?: string;
|
|
31
|
+
/** Refuse to claim with fewer than this many claim-admissible epochs left, so the claim can't be
|
|
32
|
+
* racing the refund window. Default 1 (the claim must land strictly before `refundEpoch`). */
|
|
33
|
+
minEpochsBeforeRefund?: bigint;
|
|
34
|
+
}
|
|
35
|
+
export interface HtlcFacts {
|
|
36
|
+
/** Decrypted value of the funded output (null when it didn't decrypt as ours). */
|
|
37
|
+
value: bigint | null;
|
|
38
|
+
/** Condition root the on-chain output actually commits to (null when absent/unreadable). */
|
|
39
|
+
onChainRoot: string | null;
|
|
40
|
+
/** The condition tree the claimant was handed. */
|
|
41
|
+
conditions: object[];
|
|
42
|
+
/** This account's own owner public key — must be the claim leaf's key. */
|
|
43
|
+
claimantPublicKeyHex: string;
|
|
44
|
+
currentEpoch: bigint;
|
|
45
|
+
/** When a preimage is supplied, SHA-256(preimage) must equal the tree's hash lock. */
|
|
46
|
+
preimageHashHex?: string;
|
|
47
|
+
}
|
|
48
|
+
/** Reads the hash lock and refund epoch back out of a `[claim, refund]` tree built by `htlcConditions`. */
|
|
49
|
+
export declare function describeHtlcConditions(conditions: object[]): {
|
|
50
|
+
hashLockHex: string;
|
|
51
|
+
refundEpoch: bigint;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Every reason the funded HTLC does not match what was agreed — empty means safe to reveal the
|
|
55
|
+
* preimage. Checks, in order: the output decrypts as ours; its amount; the tree's shape (rebuilt
|
|
56
|
+
* from `expected` + this account's key and compared byte-for-byte, which pins the claimant, the
|
|
57
|
+
* refunder, the hash lock and the refund epoch at once); the on-chain root equals the tree's root
|
|
58
|
+
* (so the tree we'd reveal is the one actually committed); the preimage hashes to the lock; and
|
|
59
|
+
* enough claim-admissible epochs remain.
|
|
60
|
+
*/
|
|
61
|
+
export declare function verifyHtlcTerms(facts: HtlcFacts, expected?: HtlcExpectations): string[];
|
|
62
|
+
export declare class HtlcVerificationError extends Error {
|
|
63
|
+
readonly problems: string[];
|
|
64
|
+
constructor(problems: string[]);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Whether an error from submitting/polling is a *definitive* rejection (the transaction will never
|
|
68
|
+
* execute), as opposed to an ambiguous failure (timeout, network error, service worker killed)
|
|
69
|
+
* after which the transaction may still land. Only a definitive rejection may be treated as
|
|
70
|
+
* "nothing happened"; everything else must be reconciled, never assumed failed.
|
|
71
|
+
*/
|
|
72
|
+
export declare function isDefinitiveRejection(e: unknown): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Thrown (instead of a plain error) when an HTLC transaction was, or may have been, submitted but
|
|
75
|
+
* its outcome didn't arrive in time. Carries everything the caller needs — including the refund
|
|
76
|
+
* data — and the journal entry that `OotleAccount.reconcileHtlcs()` resolves later. Callers must
|
|
77
|
+
* treat this as "pending", never as "failed".
|
|
78
|
+
*/
|
|
79
|
+
export declare class HtlcUnknownOutcomeError extends Error {
|
|
80
|
+
readonly journalId: string;
|
|
81
|
+
readonly details: {
|
|
82
|
+
kind: "fund" | "claim" | "refund";
|
|
83
|
+
transactionId?: string;
|
|
84
|
+
conditions: object[];
|
|
85
|
+
htlcCommitment?: string;
|
|
86
|
+
outputMask?: string;
|
|
87
|
+
};
|
|
88
|
+
constructor(journalId: string, details: {
|
|
89
|
+
kind: "fund" | "claim" | "refund";
|
|
90
|
+
transactionId?: string;
|
|
91
|
+
conditions: object[];
|
|
92
|
+
htlcCommitment?: string;
|
|
93
|
+
outputMask?: string;
|
|
94
|
+
}, cause: unknown);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* `(a - b) mod ℓ` for 32-byte little-endian scalars. A transfer's `outputMask` is the *sum* of its
|
|
98
|
+
* outputs' blinding masks (`aggregated_output_mask`), so when a fund creates the HTLC output plus
|
|
99
|
+
* this account's own change, the HTLC output's own mask is `aggregate - changeMask` — the change
|
|
100
|
+
* mask being recoverable because the change is addressed to this account. The result is only ever
|
|
101
|
+
* trusted after `OotleAccount.htlcFund` proves it reproduces the HTLC commitment (see there).
|
|
102
|
+
*/
|
|
103
|
+
export declare function subtractScalars(aHex: string, bHex: string): string;
|
|
104
|
+
/**
|
|
105
|
+
* Decrypts one of this account's *own* outputs straight out of a locally built outputs statement
|
|
106
|
+
* (before anything is submitted) and returns its value and blinding mask. `outputJson` is the
|
|
107
|
+
* statement's `outputs[i]` entry; `viewSecret` this account's view secret. Throws if the output
|
|
108
|
+
* isn't addressed to this view key (AEAD or commitment check fails inside `unblindOutput`).
|
|
109
|
+
*/
|
|
110
|
+
export declare function decryptOwnStatementOutput(outputJson: unknown, viewSecret: Uint8Array): {
|
|
111
|
+
value: bigint;
|
|
112
|
+
maskHex: string;
|
|
113
|
+
commitment: string;
|
|
114
|
+
};
|
|
115
|
+
/** The Pedersen commitment of input `index` in a full statement JSON (what a spend of it commits to). */
|
|
116
|
+
export declare function statementInputCommitment(statementJson: string, index?: number): string | null;
|
|
117
|
+
/** The commitment of output `index` in a full `StealthTransferStatement` JSON string. Only the
|
|
118
|
+
* commitment strings are read, so parsing precision on large numeric fields doesn't matter here. */
|
|
119
|
+
export declare function statementOutputCommitment(statementJson: string, index?: number): string;
|
|
120
|
+
export declare function sha256Hex(data: Uint8Array): Promise<string>;
|
|
121
|
+
/** A fresh, random operation id (journal ids and reservation holders). */
|
|
122
|
+
export declare function newOperationId(prefix: string): string;
|
|
123
|
+
//# sourceMappingURL=htlcSafety.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"htlcSafety.d.ts","sourceRoot":"","sources":["../src/htlcSafety.ts"],"names":[],"mappings":"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"}
|