@unicitylabs/bridge-plugin 0.2.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/DEMO.md +255 -0
  2. package/README.md +89 -0
  3. package/lib/BridgeLockJustification.d.ts +31 -0
  4. package/lib/BridgeLockJustification.js +41 -0
  5. package/lib/BridgeMintJustificationVerifier.d.ts +12 -0
  6. package/lib/BridgeMintJustificationVerifier.js +35 -0
  7. package/lib/LockMintJustificationVerifier.d.ts +42 -0
  8. package/lib/LockMintJustificationVerifier.js +108 -0
  9. package/lib/address.d.ts +13 -0
  10. package/lib/address.js +71 -0
  11. package/lib/bridge-back/abi.d.ts +30 -0
  12. package/lib/bridge-back/abi.js +89 -0
  13. package/lib/bridge-back/burn.d.ts +79 -0
  14. package/lib/bridge-back/burn.js +78 -0
  15. package/lib/bridge-back/burned-blob.d.ts +6 -0
  16. package/lib/bridge-back/burned-blob.js +16 -0
  17. package/lib/bridge-back/cbor.d.ts +32 -0
  18. package/lib/bridge-back/cbor.js +146 -0
  19. package/lib/bridge-back/derivations.d.ts +112 -0
  20. package/lib/bridge-back/derivations.js +201 -0
  21. package/lib/bridge-back/index.d.ts +11 -0
  22. package/lib/bridge-back/index.js +11 -0
  23. package/lib/cli/main.d.ts +2 -0
  24. package/lib/cli/main.js +149 -0
  25. package/lib/cli/scenario.d.ts +36 -0
  26. package/lib/cli/scenario.js +94 -0
  27. package/lib/config.d.ts +25 -0
  28. package/lib/config.js +1 -0
  29. package/lib/contract-call.d.ts +13 -0
  30. package/lib/contract-call.js +19 -0
  31. package/lib/evm/EvmRpcClient.d.ts +25 -0
  32. package/lib/evm/EvmRpcClient.js +54 -0
  33. package/lib/evm/config.d.ts +6 -0
  34. package/lib/evm/config.js +6 -0
  35. package/lib/evm/family.d.ts +3 -0
  36. package/lib/evm/family.js +21 -0
  37. package/lib/evm/index.d.ts +6 -0
  38. package/lib/evm/index.js +6 -0
  39. package/lib/evm/presentation.d.ts +4 -0
  40. package/lib/evm/presentation.js +21 -0
  41. package/lib/evm/providers.d.ts +3 -0
  42. package/lib/evm/providers.js +9 -0
  43. package/lib/evm/signer.d.ts +43 -0
  44. package/lib/evm/signer.js +91 -0
  45. package/lib/families.d.ts +4 -0
  46. package/lib/families.js +11 -0
  47. package/lib/family.d.ts +16 -0
  48. package/lib/family.js +1 -0
  49. package/lib/hex.d.ts +4 -0
  50. package/lib/hex.js +33 -0
  51. package/lib/identifiers.d.ts +14 -0
  52. package/lib/identifiers.js +22 -0
  53. package/lib/index.d.ts +48 -0
  54. package/lib/index.js +55 -0
  55. package/lib/lock-event.d.ts +28 -0
  56. package/lib/lock-event.js +48 -0
  57. package/lib/source-chain.d.ts +29 -0
  58. package/lib/source-chain.js +1 -0
  59. package/lib/tron/TronRpcClient.d.ts +39 -0
  60. package/lib/tron/TronRpcClient.js +100 -0
  61. package/lib/tron/config.d.ts +11 -0
  62. package/lib/tron/config.js +31 -0
  63. package/lib/tron/family.d.ts +4 -0
  64. package/lib/tron/family.js +18 -0
  65. package/lib/tron/presentation.d.ts +10 -0
  66. package/lib/tron/presentation.js +18 -0
  67. package/lib/tron/providers.d.ts +46 -0
  68. package/lib/tron/providers.js +32 -0
  69. package/lib/tron/signer.d.ts +166 -0
  70. package/lib/tron/signer.js +238 -0
  71. package/lib/value.d.ts +23 -0
  72. package/lib/value.js +58 -0
  73. package/lib/wallet/allowance.d.ts +20 -0
  74. package/lib/wallet/allowance.js +27 -0
  75. package/lib/wallet/backing.d.ts +2 -0
  76. package/lib/wallet/backing.js +15 -0
  77. package/lib/wallet/facade.d.ts +97 -0
  78. package/lib/wallet/facade.js +100 -0
  79. package/lib/wallet/finality.d.ts +8 -0
  80. package/lib/wallet/finality.js +28 -0
  81. package/lib/wallet/index.d.ts +25 -0
  82. package/lib/wallet/index.js +25 -0
  83. package/lib/wallet/manifest.d.ts +69 -0
  84. package/lib/wallet/manifest.js +81 -0
  85. package/lib/wallet/manifests.d.ts +31 -0
  86. package/lib/wallet/manifests.js +70 -0
  87. package/lib/wallet/payout.d.ts +5 -0
  88. package/lib/wallet/payout.js +14 -0
  89. package/lib/wallet/registry.d.ts +40 -0
  90. package/lib/wallet/registry.js +34 -0
  91. package/lib/wallet/return-client.d.ts +91 -0
  92. package/lib/wallet/return-client.js +100 -0
  93. package/lib/wallet/self-mint-verifier.d.ts +10 -0
  94. package/lib/wallet/self-mint-verifier.js +5 -0
  95. package/lib/wallet/signer.d.ts +18 -0
  96. package/lib/wallet/signer.js +1 -0
  97. package/lib/wallet/source-adapter.d.ts +33 -0
  98. package/lib/wallet/source-adapter.js +99 -0
  99. package/lib/wallet/token-plugin.d.ts +4 -0
  100. package/lib/wallet/token-plugin.js +20 -0
  101. package/manifests/bridges.nile.json +23 -0
  102. package/package.json +71 -0
package/lib/address.js ADDED
@@ -0,0 +1,71 @@
1
+ import { sha256 } from '@noble/hashes/sha2.js';
2
+ import { fromHex, toHex } from './hex.js';
3
+ const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
4
+ const BASE58_MAP = {};
5
+ for (let i = 0; i < BASE58_ALPHABET.length; i++) {
6
+ BASE58_MAP[BASE58_ALPHABET[i]] = i;
7
+ }
8
+ /** Tron mainnet address prefix byte (0x41). */
9
+ export const TRON_ADDRESS_PREFIX = 0x41;
10
+ function base58Decode(input) {
11
+ const bytes = [0];
12
+ for (const ch of input) {
13
+ const value = BASE58_MAP[ch];
14
+ if (value === undefined) {
15
+ throw new Error(`Invalid base58 character: ${ch}`);
16
+ }
17
+ let carry = value;
18
+ for (let j = 0; j < bytes.length; j++) {
19
+ carry += bytes[j] * 58;
20
+ bytes[j] = carry & 0xff;
21
+ carry >>= 8;
22
+ }
23
+ while (carry > 0) {
24
+ bytes.push(carry & 0xff);
25
+ carry >>= 8;
26
+ }
27
+ }
28
+ // Leading '1's encode leading zero bytes.
29
+ for (let k = 0; k < input.length && input[k] === '1'; k++) {
30
+ bytes.push(0);
31
+ }
32
+ return Uint8Array.from(bytes.reverse());
33
+ }
34
+ /**
35
+ * Normalize any source-chain address form to the 20-byte EVM-style address as
36
+ * lowercase hex (no `0x`, no `41` prefix), the form event logs use for `address`
37
+ * on both families.
38
+ *
39
+ * Accepts:
40
+ * - Tron base58check `T...` addresses,
41
+ * - Tron `41`-prefixed 21-byte hex (TronWeb `toHex()` form),
42
+ * - bare 20-byte hex, optionally `0x`-prefixed (Ethereum, or either family's raw form).
43
+ */
44
+ export function toEvmAddressHex(address) {
45
+ if (address.startsWith('T')) {
46
+ const decoded = base58Decode(address);
47
+ if (decoded.length !== 25) {
48
+ throw new Error(`Invalid Tron base58 address length: ${address}`);
49
+ }
50
+ const payload = decoded.subarray(0, 21);
51
+ const checksum = decoded.subarray(21);
52
+ const expected = sha256(sha256(payload)).subarray(0, 4);
53
+ for (let i = 0; i < 4; i++) {
54
+ if (checksum[i] !== expected[i]) {
55
+ throw new Error(`Bad checksum for Tron address: ${address}`);
56
+ }
57
+ }
58
+ if (payload[0] !== TRON_ADDRESS_PREFIX) {
59
+ throw new Error(`Unexpected Tron address prefix: ${address}`);
60
+ }
61
+ return toHex(payload.subarray(1));
62
+ }
63
+ const bytes = fromHex(address);
64
+ if (bytes.length === 21 && bytes[0] === TRON_ADDRESS_PREFIX) {
65
+ return toHex(bytes.subarray(1));
66
+ }
67
+ if (bytes.length === 20) {
68
+ return toHex(bytes);
69
+ }
70
+ throw new Error(`Unrecognized address form: ${address}`);
71
+ }
@@ -0,0 +1,30 @@
1
+ export type Val = {
2
+ t: 'str';
3
+ v: string;
4
+ } | {
5
+ t: 'u32';
6
+ v: number | bigint;
7
+ } | {
8
+ t: 'u64';
9
+ v: number | bigint;
10
+ } | {
11
+ t: 'u256';
12
+ v: bigint;
13
+ } | {
14
+ t: 'addr';
15
+ v: Uint8Array;
16
+ } | {
17
+ t: 'b32';
18
+ v: Uint8Array;
19
+ };
20
+ export declare const Str: (v: string) => Val;
21
+ export declare const U32: (v: number | bigint) => Val;
22
+ export declare const U64: (v: number | bigint) => Val;
23
+ export declare const U256: (v: bigint) => Val;
24
+ export declare const Addr: (v: Uint8Array) => Val;
25
+ export declare const B32: (v: Uint8Array) => Val;
26
+ export declare function keccak256(input: Uint8Array): Uint8Array;
27
+ /** Solidity `abi.encode(vals...)` with proper head/tail framing. */
28
+ export declare function encode(vals: Val[]): Uint8Array;
29
+ /** Concatenated fixed-width 32-byte words, no array framing (00 §7). */
30
+ export declare function packWords(vals: Val[]): Uint8Array;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Minimal Solidity `abi.encode` for the static types the vault recomputes, plus
3
+ * the single dynamic `string` domain literal used inside `configHash`/`lockDigest`.
4
+ *
5
+ * Mirrors `protocol/vectors/gen/src/abi.rs` byte-for-byte (interop §1, §7). `encode`
6
+ * does proper head/tail framing; `packWords` is the non-framed 32-byte-word
7
+ * concatenation the vault keccak-hashes for `returnRoot`/`lockRefRoot`.
8
+ */
9
+ import { keccak_256 } from '@noble/hashes/sha3.js';
10
+ export const Str = (v) => ({ t: 'str', v });
11
+ export const U32 = (v) => ({ t: 'u32', v });
12
+ export const U64 = (v) => ({ t: 'u64', v });
13
+ export const U256 = (v) => ({ t: 'u256', v });
14
+ export const Addr = (v) => ({ t: 'addr', v });
15
+ export const B32 = (v) => ({ t: 'b32', v });
16
+ export function keccak256(input) {
17
+ return keccak_256(input);
18
+ }
19
+ /** 32-byte big-endian word for a small unsigned integer. */
20
+ function wordU(n) {
21
+ const w = new Uint8Array(32);
22
+ for (let i = 0; i < 32 && n > 0n; i++) {
23
+ w[31 - i] = Number(n & 0xffn);
24
+ n >>= 8n;
25
+ }
26
+ return w;
27
+ }
28
+ /** The 32-byte head word for a static value (numbers/addresses left-padded). */
29
+ function staticWord(v) {
30
+ switch (v.t) {
31
+ case 'u32':
32
+ case 'u64':
33
+ return wordU(BigInt(v.v));
34
+ case 'u256':
35
+ return wordU(v.v);
36
+ case 'addr': {
37
+ if (v.v.length !== 20)
38
+ throw new Error('addr must be 20 bytes');
39
+ const w = new Uint8Array(32);
40
+ w.set(v.v, 12);
41
+ return w;
42
+ }
43
+ case 'b32': {
44
+ if (v.v.length !== 32)
45
+ throw new Error('b32 must be 32 bytes');
46
+ return v.v.slice();
47
+ }
48
+ case 'str':
49
+ throw new Error('dynamic value has no head word');
50
+ }
51
+ }
52
+ function concat(parts) {
53
+ let len = 0;
54
+ for (const p of parts)
55
+ len += p.length;
56
+ const out = new Uint8Array(len);
57
+ let off = 0;
58
+ for (const p of parts) {
59
+ out.set(p, off);
60
+ off += p.length;
61
+ }
62
+ return out;
63
+ }
64
+ /** Solidity `abi.encode(vals...)` with proper head/tail framing. */
65
+ export function encode(vals) {
66
+ const headSize = 32 * vals.length;
67
+ const heads = [];
68
+ const tails = [];
69
+ let tailLen = 0;
70
+ for (const v of vals) {
71
+ if (v.t === 'str') {
72
+ heads.push(wordU(BigInt(headSize + tailLen)));
73
+ const data = new TextEncoder().encode(v.v);
74
+ const padded = new Uint8Array(Math.ceil(data.length / 32) * 32);
75
+ padded.set(data);
76
+ const piece = concat([wordU(BigInt(data.length)), padded]);
77
+ tails.push(piece);
78
+ tailLen += piece.length;
79
+ }
80
+ else {
81
+ heads.push(staticWord(v));
82
+ }
83
+ }
84
+ return concat([...heads, ...tails]);
85
+ }
86
+ /** Concatenated fixed-width 32-byte words, no array framing (00 §7). */
87
+ export function packWords(vals) {
88
+ return concat(vals.map(staticWord));
89
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Bridge-back burn construction (02-ts-sdk-and-wallet.md §2a–2c).
3
+ *
4
+ * The wallet burns the source token so the terminal transfer carries the
5
+ * canonical `reasonBytes` in its **auxiliary data** and its recipient predicate
6
+ * is `BurnPredicate(H(reasonBytes))` — *not* `BurnPredicate(reasonBytes)` (00 §4).
7
+ * The reason is then fully self-contained in the burned blob: the circuit reads
8
+ * `reasonBytes` from the certified aux data, recomputes `reasonHash`, requires
9
+ * the terminal predicate to equal `BurnPredicate(reasonHash)`, and decodes the
10
+ * fields. This module produces exactly those bytes plus the read-only return
11
+ * preview and the prover hand-off envelope. It holds no authority and never
12
+ * proves or settles.
13
+ */
14
+ import { BurnPredicate } from '@unicitylabs/state-transition-sdk/lib/predicate/builtin/BurnPredicate.js';
15
+ import type { Token } from '@unicitylabs/state-transition-sdk/lib/transaction/Token.js';
16
+ import { TransferTransaction } from '@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction.js';
17
+ import { type BridgeBackReason, type BridgeConfig, type ReturnLeaf } from './derivations.js';
18
+ /** The reason bytes + the predicate that binds them, ready to attach to a burn. */
19
+ export interface BridgeBackBurnReason {
20
+ /** Canonical `BridgeBackReason` CBOR; rides in the burn transfer's aux data. */
21
+ readonly reasonBytes: Uint8Array;
22
+ /** `H(reasonBytes)` — the payload of the binding {BurnPredicate}. */
23
+ readonly reasonHash: Uint8Array;
24
+ /** `BurnPredicate(H(reasonBytes))` — the terminal recipient predicate. */
25
+ readonly burnPredicate: BurnPredicate;
26
+ }
27
+ /**
28
+ * Build the canonical reason + its binding {BurnPredicate}. Pure (no SDK token
29
+ * needed) so a wallet can preview the exact bytes before committing the burn.
30
+ */
31
+ export declare function buildBridgeBackBurnReason(c: BridgeConfig, r: BridgeBackReason): BridgeBackBurnReason;
32
+ /**
33
+ * Construct the terminal burn transfer for `token`: recipient is
34
+ * `BurnPredicate(H(reasonBytes))` and the aux data is the canonical
35
+ * `reasonBytes`. For a partial return, split first (existing split flow) and
36
+ * pass the child whose value equals `amount`. The caller certifies/submits this
37
+ * transfer through its normal SDK path; the resulting burned blob is the
38
+ * release-authorizing recovery material (ZK_BACK3 §13).
39
+ *
40
+ * @param token The (whole) token to burn.
41
+ * @param c Deployment config (binds the reason's config fields).
42
+ * @param r The return parameters (recipient/amount/fee/deadline).
43
+ * @param stateMask State mask mixed into the new state (per the SDK transfer API).
44
+ */
45
+ export declare function createBridgeBackBurnTransfer(token: Token, c: BridgeConfig, r: BridgeBackReason, stateMask: Uint8Array): Promise<{
46
+ transfer: TransferTransaction;
47
+ reason: BridgeBackBurnReason;
48
+ }>;
49
+ /** The read-only return preview the wallet shows the user (02 §2b). */
50
+ export interface ReturnPreview {
51
+ readonly burnTransitionId: Uint8Array;
52
+ readonly nullifier: Uint8Array;
53
+ readonly returnLeaf: ReturnLeaf;
54
+ }
55
+ /**
56
+ * Derive the pending nullifier + settlement leaf for a burn, given the certified
57
+ * burn state id / tx hash (available once the burn is certified). Read-only:
58
+ * the prover computes the identical values, and a TS relayer reuses this (02 §2b).
59
+ */
60
+ export declare function previewReturn(configHash: Uint8Array, r: BridgeBackReason, burnStateId: Uint8Array, burnTxHash: Uint8Array): ReturnPreview;
61
+ /**
62
+ * The witness-request envelope the wallet/relayer posts to a prover (02 §2c).
63
+ * The wallet supplies only what it owns; the prover fetches anchor + inclusion
64
+ * proofs itself. `reasonBytes` are included for convenience, but they are also
65
+ * recoverable from `tokenCbor` (the burn's aux data) — the blob is self-contained.
66
+ */
67
+ export interface WitnessRequest {
68
+ readonly tokenCbor: Uint8Array;
69
+ readonly configHash: Uint8Array;
70
+ readonly reasonBytes: Uint8Array;
71
+ readonly anchorHint?: bigint;
72
+ }
73
+ /** Assemble the prover hand-off envelope. */
74
+ export declare function buildWitnessRequest(args: {
75
+ tokenCbor: Uint8Array;
76
+ configHash: Uint8Array;
77
+ reasonBytes: Uint8Array;
78
+ anchorHint?: bigint;
79
+ }): WitnessRequest;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Bridge-back burn construction (02-ts-sdk-and-wallet.md §2a–2c).
3
+ *
4
+ * The wallet burns the source token so the terminal transfer carries the
5
+ * canonical `reasonBytes` in its **auxiliary data** and its recipient predicate
6
+ * is `BurnPredicate(H(reasonBytes))` — *not* `BurnPredicate(reasonBytes)` (00 §4).
7
+ * The reason is then fully self-contained in the burned blob: the circuit reads
8
+ * `reasonBytes` from the certified aux data, recomputes `reasonHash`, requires
9
+ * the terminal predicate to equal `BurnPredicate(reasonHash)`, and decodes the
10
+ * fields. This module produces exactly those bytes plus the read-only return
11
+ * preview and the prover hand-off envelope. It holds no authority and never
12
+ * proves or settles.
13
+ */
14
+ import { BurnPredicate } from '@unicitylabs/state-transition-sdk/lib/predicate/builtin/BurnPredicate.js';
15
+ import { StateMask } from '@unicitylabs/state-transition-sdk/lib/transaction/StateMask.js';
16
+ import { TransferTransaction } from '@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction.js';
17
+ import { burnTransitionId, encodeBridgeBackReason, nullifier as deriveNullifier, reasonHash as deriveReasonHash, } from './derivations.js';
18
+ /**
19
+ * Build the canonical reason + its binding {BurnPredicate}. Pure (no SDK token
20
+ * needed) so a wallet can preview the exact bytes before committing the burn.
21
+ */
22
+ export function buildBridgeBackBurnReason(c, r) {
23
+ const reasonBytes = encodeBridgeBackReason(c, r);
24
+ const reasonHash = deriveReasonHash(reasonBytes);
25
+ return {
26
+ reasonBytes,
27
+ reasonHash,
28
+ burnPredicate: BurnPredicate.create(reasonHash),
29
+ };
30
+ }
31
+ /**
32
+ * Construct the terminal burn transfer for `token`: recipient is
33
+ * `BurnPredicate(H(reasonBytes))` and the aux data is the canonical
34
+ * `reasonBytes`. For a partial return, split first (existing split flow) and
35
+ * pass the child whose value equals `amount`. The caller certifies/submits this
36
+ * transfer through its normal SDK path; the resulting burned blob is the
37
+ * release-authorizing recovery material (ZK_BACK3 §13).
38
+ *
39
+ * @param token The (whole) token to burn.
40
+ * @param c Deployment config (binds the reason's config fields).
41
+ * @param r The return parameters (recipient/amount/fee/deadline).
42
+ * @param stateMask State mask mixed into the new state (per the SDK transfer API).
43
+ */
44
+ export async function createBridgeBackBurnTransfer(token, c, r, stateMask) {
45
+ const reason = buildBridgeBackBurnReason(c, r);
46
+ const transfer = await TransferTransaction.create(token, reason.burnPredicate, StateMask.fromBytes(stateMask), { data: reason.reasonBytes });
47
+ return { transfer, reason };
48
+ }
49
+ /**
50
+ * Derive the pending nullifier + settlement leaf for a burn, given the certified
51
+ * burn state id / tx hash (available once the burn is certified). Read-only:
52
+ * the prover computes the identical values, and a TS relayer reuses this (02 §2b).
53
+ */
54
+ export function previewReturn(configHash, r, burnStateId, burnTxHash) {
55
+ const btId = burnTransitionId(burnStateId, burnTxHash);
56
+ const nullifier = deriveNullifier(configHash, btId);
57
+ return {
58
+ burnTransitionId: btId,
59
+ nullifier,
60
+ returnLeaf: {
61
+ nullifier,
62
+ recipient: r.recipient,
63
+ amount: r.amount,
64
+ feeRecipient: r.feeRecipient,
65
+ feeAmount: r.feeAmount,
66
+ deadline: r.deadline,
67
+ },
68
+ };
69
+ }
70
+ /** Assemble the prover hand-off envelope. */
71
+ export function buildWitnessRequest(args) {
72
+ const req = {
73
+ tokenCbor: args.tokenCbor,
74
+ configHash: args.configHash,
75
+ reasonBytes: args.reasonBytes,
76
+ };
77
+ return args.anchorHint === undefined ? req : { ...req, anchorHint: args.anchorHint };
78
+ }
@@ -0,0 +1,6 @@
1
+ export interface BurnIdentifiers {
2
+ readonly burnStateId: Uint8Array;
3
+ readonly burnTxHash: Uint8Array;
4
+ readonly reasonBytes: Uint8Array;
5
+ }
6
+ export declare function burnIdentifiers(burnedToken: Uint8Array): Promise<BurnIdentifiers>;
@@ -0,0 +1,16 @@
1
+ import { StateId } from '@unicitylabs/state-transition-sdk/lib/api/StateId.js';
2
+ import { Token } from '@unicitylabs/state-transition-sdk/lib/transaction/Token.js';
3
+ export async function burnIdentifiers(burnedToken) {
4
+ const token = await Token.fromCBOR(burnedToken);
5
+ const burn = token.transactions.at(-1);
6
+ if (!burn)
7
+ throw new Error('burnIdentifiers: the blob has no transfer; it is not a burned token');
8
+ const reasonBytes = burn.data;
9
+ if (!reasonBytes)
10
+ throw new Error('burnIdentifiers: the terminal transfer carries no reason bytes');
11
+ return {
12
+ burnStateId: (await StateId.fromTransaction(burn)).data,
13
+ burnTxHash: (await burn.calculateTransactionHash()).data,
14
+ reasonBytes: new Uint8Array(reasonBytes),
15
+ };
16
+ }
@@ -0,0 +1,32 @@
1
+ declare function concat(parts: Uint8Array[]): Uint8Array;
2
+ /** major type 0: unsigned integer. */
3
+ export declare function uint(n: number | bigint): Uint8Array;
4
+ /** major type 2: byte string. */
5
+ export declare function bytes(b: Uint8Array): Uint8Array;
6
+ /** major type 3: text string. */
7
+ export declare function text(s: string): Uint8Array;
8
+ /** major type 4: array header (followed by `len` items). */
9
+ export declare function arrayHeader(len: number): Uint8Array;
10
+ /** major type 6: semantic tag. */
11
+ export declare function tag(t: number | bigint): Uint8Array;
12
+ /** Concatenate pre-encoded CBOR items. */
13
+ export { concat as concatBytes };
14
+ /** `H(fields...) = SHA-256( CBOR-array(fields) )` (00 §8 / appendix convention). */
15
+ export declare function hArray(items: Uint8Array[]): Uint8Array;
16
+ /** Strip leading zero bytes (minimal big-endian); zero becomes the empty string. */
17
+ export declare function minimalBe(b: Uint8Array): Uint8Array;
18
+ /** A cursor over CBOR bytes. */
19
+ export declare class CborReader {
20
+ readonly buf: Uint8Array;
21
+ pos: number;
22
+ constructor(buf: Uint8Array);
23
+ get done(): boolean;
24
+ /** Read one item head; returns its major type and argument. Rejects
25
+ * non-minimal (non-canonical) argument encodings. */
26
+ private head;
27
+ private expect;
28
+ readTag(): bigint;
29
+ readArrayHeader(): number;
30
+ readUint(): bigint;
31
+ readBytes(): Uint8Array;
32
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Minimal deterministic (canonical) CBOR encoder — only the item types the
3
+ * bridge structures use: definite-length, minimal-length integer arguments.
4
+ *
5
+ * This mirrors `protocol/vectors/gen/src/cbor.rs` byte-for-byte; it is the TS half
6
+ * of the cross-stack contract (00 §4, §5, §8). `H(...)` is SHA-256 over a CBOR
7
+ * array of fields. Conformance is asserted against `protocol/vectors/{reason,
8
+ * nullifier}` — do not "improve" the encoding without bumping BRIDGE_PROTO_VERSION.
9
+ */
10
+ import { sha256 } from '@noble/hashes/sha2.js';
11
+ /** major<<5 | argument, minimal-length per canonical CBOR. */
12
+ function head(major, arg) {
13
+ const m = major << 5;
14
+ const n = BigInt(arg);
15
+ if (n < 24n)
16
+ return Uint8Array.of(m | Number(n));
17
+ if (n <= 0xffn)
18
+ return Uint8Array.of(m | 24, Number(n));
19
+ if (n <= 0xffffn) {
20
+ return Uint8Array.of(m | 25, Number((n >> 8n) & 0xffn), Number(n & 0xffn));
21
+ }
22
+ if (n <= 0xffffffffn) {
23
+ return Uint8Array.of(m | 26, Number((n >> 24n) & 0xffn), Number((n >> 16n) & 0xffn), Number((n >> 8n) & 0xffn), Number(n & 0xffn));
24
+ }
25
+ const out = new Uint8Array(9);
26
+ out[0] = m | 27;
27
+ for (let i = 0; i < 8; i++)
28
+ out[8 - i] = Number((n >> BigInt(8 * i)) & 0xffn);
29
+ return out;
30
+ }
31
+ function concat(parts) {
32
+ let len = 0;
33
+ for (const p of parts)
34
+ len += p.length;
35
+ const out = new Uint8Array(len);
36
+ let off = 0;
37
+ for (const p of parts) {
38
+ out.set(p, off);
39
+ off += p.length;
40
+ }
41
+ return out;
42
+ }
43
+ /** major type 0: unsigned integer. */
44
+ export function uint(n) {
45
+ return head(0, n);
46
+ }
47
+ /** major type 2: byte string. */
48
+ export function bytes(b) {
49
+ return concat([head(2, b.length), b]);
50
+ }
51
+ /** major type 3: text string. */
52
+ export function text(s) {
53
+ const enc = new TextEncoder().encode(s);
54
+ return concat([head(3, enc.length), enc]);
55
+ }
56
+ /** major type 4: array header (followed by `len` items). */
57
+ export function arrayHeader(len) {
58
+ return head(4, len);
59
+ }
60
+ /** major type 6: semantic tag. */
61
+ export function tag(t) {
62
+ return head(6, t);
63
+ }
64
+ /** Concatenate pre-encoded CBOR items. */
65
+ export { concat as concatBytes };
66
+ /** `H(fields...) = SHA-256( CBOR-array(fields) )` (00 §8 / appendix convention). */
67
+ export function hArray(items) {
68
+ return sha256(concat([arrayHeader(items.length), ...items]));
69
+ }
70
+ /** Strip leading zero bytes (minimal big-endian); zero becomes the empty string. */
71
+ export function minimalBe(b) {
72
+ let first = 0;
73
+ while (first < b.length && b[first] === 0)
74
+ first++;
75
+ return b.slice(first);
76
+ }
77
+ // --- canonical reader (decode side) ----------------------------------------
78
+ // Only the item types the bridge structures use, with strict canonical checks:
79
+ // minimal-length argument, no trailing bytes (the caller asserts full
80
+ // consumption). Mirrors the encoder above so a blob round-trips exactly.
81
+ /** A cursor over CBOR bytes. */
82
+ export class CborReader {
83
+ buf;
84
+ pos = 0;
85
+ constructor(buf) {
86
+ this.buf = buf;
87
+ }
88
+ get done() {
89
+ return this.pos >= this.buf.length;
90
+ }
91
+ /** Read one item head; returns its major type and argument. Rejects
92
+ * non-minimal (non-canonical) argument encodings. */
93
+ head() {
94
+ if (this.done)
95
+ throw new Error('CBOR: unexpected end');
96
+ const ib = this.buf[this.pos++];
97
+ const major = ib >> 5;
98
+ const ai = ib & 0x1f;
99
+ if (ai < 24)
100
+ return { major, arg: BigInt(ai) };
101
+ let len;
102
+ if (ai === 24)
103
+ len = 1;
104
+ else if (ai === 25)
105
+ len = 2;
106
+ else if (ai === 26)
107
+ len = 4;
108
+ else if (ai === 27)
109
+ len = 8;
110
+ else
111
+ throw new Error(`CBOR: bad additional info ${ai}`);
112
+ if (this.pos + len > this.buf.length)
113
+ throw new Error('CBOR: truncated argument');
114
+ let arg = 0n;
115
+ for (let i = 0; i < len; i++)
116
+ arg = (arg << 8n) | BigInt(this.buf[this.pos++]);
117
+ // canonical: the value must not fit in a shorter encoding
118
+ const min = ai === 24 ? 24n : ai === 25 ? 0x100n : ai === 26 ? 0x10000n : 0x100000000n;
119
+ if (arg < min)
120
+ throw new Error('CBOR: non-canonical (non-minimal) integer');
121
+ return { major, arg };
122
+ }
123
+ expect(major, what) {
124
+ const h = this.head();
125
+ if (h.major !== major)
126
+ throw new Error(`CBOR: expected ${what} (major ${major}), got major ${h.major}`);
127
+ return h.arg;
128
+ }
129
+ readTag() {
130
+ return this.expect(6, 'tag');
131
+ }
132
+ readArrayHeader() {
133
+ return Number(this.expect(4, 'array'));
134
+ }
135
+ readUint() {
136
+ return this.expect(0, 'uint');
137
+ }
138
+ readBytes() {
139
+ const len = Number(this.expect(2, 'byte string'));
140
+ if (this.pos + len > this.buf.length)
141
+ throw new Error('CBOR: truncated byte string');
142
+ const out = this.buf.slice(this.pos, this.pos + len);
143
+ this.pos += len;
144
+ return out;
145
+ }
146
+ }
@@ -0,0 +1,112 @@
1
+ export declare const DOMAIN_CONFIG = "unicity-bridge-return-config:v1";
2
+ export declare const DOMAIN_LOCK = "unicity-bridge-lock:v1";
3
+ export declare const DOMAIN_RETURN = "unicity-bridge-return:v1";
4
+ export declare const DOMAIN_BURN_TRANSITION = "unicity-burn-transition:v1";
5
+ export declare const DOMAIN_NULLIFIER = "unicity-bridge-return-nullifier:v1";
6
+ /** Deployment configuration (00 §2). Byte fields are raw (no `0x`). */
7
+ export interface BridgeConfig {
8
+ readonly sourceChainId: bigint;
9
+ readonly vault: Uint8Array;
10
+ readonly asset: Uint8Array;
11
+ readonly tokenType: Uint8Array;
12
+ readonly coinId: Uint8Array;
13
+ readonly reasonTag: bigint;
14
+ readonly lockDomain: Uint8Array;
15
+ readonly nullifierDomain: Uint8Array;
16
+ }
17
+ /** 32-byte big-endian representation of an unsigned integer. */
18
+ export declare function u256be(n: bigint): Uint8Array;
19
+ /** `configHash = K(abi.encode("...config:v1", fields...))` (00 §2). */
20
+ export declare function configHash(c: BridgeConfig): Uint8Array;
21
+ /** `domainTag = K("unicity-bridge-return:v1")` over raw bytes (00 §7). */
22
+ export declare function domainTag(): Uint8Array;
23
+ /** `lockDigest = K(abi.encode("...lock:v1", fields...))` (00 §3). */
24
+ export declare function lockDigest(params: {
25
+ sourceChainId: bigint;
26
+ vault: Uint8Array;
27
+ nonce: bigint;
28
+ asset: Uint8Array;
29
+ tokenType: Uint8Array;
30
+ coinId: Uint8Array;
31
+ amount: bigint;
32
+ unicityTokenId: Uint8Array;
33
+ recipientCommitment: Uint8Array;
34
+ }): Uint8Array;
35
+ /** Per-return parameters of a `BridgeBackReason` (00 §4); config-bound fields
36
+ * (sourceChainId/vault/asset/tokenType/coinId) come from {BridgeConfig}. */
37
+ export interface BridgeBackReason {
38
+ readonly version: bigint;
39
+ readonly recipient: Uint8Array;
40
+ readonly amount: bigint;
41
+ readonly feeRecipient: Uint8Array;
42
+ readonly feeAmount: bigint;
43
+ readonly deadline: bigint;
44
+ }
45
+ /**
46
+ * Canonical CBOR of the 11-field `BridgeBackReason` array under `reasonTag`
47
+ * (00 §4). This is the `reasonBytes` that ride in the terminal burn's auxiliary
48
+ * data; the burn predicate binds `H(reasonBytes)` (see {reasonHash}).
49
+ */
50
+ export declare function encodeBridgeBackReason(c: BridgeConfig, r: BridgeBackReason): Uint8Array;
51
+ /** `reasonHash = H(reasonBytes)` — the value `BurnPredicate(H(reasonBytes))`
52
+ * binds (00 §4, PROVISIONAL preimage pending M0 SDK confirmation). */
53
+ export declare function reasonHash(reasonBytes: Uint8Array): Uint8Array;
54
+ /** The fully-decoded `BridgeBackReason`, including its config-bound fields. */
55
+ export interface DecodedBridgeBackReason {
56
+ readonly reasonTag: bigint;
57
+ readonly version: bigint;
58
+ readonly sourceChainId: bigint;
59
+ readonly vault: Uint8Array;
60
+ readonly asset: Uint8Array;
61
+ readonly tokenType: Uint8Array;
62
+ readonly coinId: Uint8Array;
63
+ readonly recipient: Uint8Array;
64
+ readonly amount: bigint;
65
+ readonly feeRecipient: Uint8Array;
66
+ readonly feeAmount: bigint;
67
+ readonly deadline: bigint;
68
+ }
69
+ /**
70
+ * Inverse of {encodeBridgeBackReason}: decode the canonical `reasonBytes` a
71
+ * burned token blob carries (the circuit performs the same decode). Strict —
72
+ * rejects non-canonical CBOR and any trailing bytes (00 §4). `amount`/`feeAmount`
73
+ * come back as the minimal-big-endian byte strings re-read as integers.
74
+ */
75
+ export declare function decodeBridgeBackReason(reasonBytes: Uint8Array): DecodedBridgeBackReason;
76
+ /** `burnTransitionId = H("unicity-burn-transition:v1", stateId, txHash)` (00 §5). */
77
+ export declare function burnTransitionId(stateId: Uint8Array, txHash: Uint8Array): Uint8Array;
78
+ /** `nullifier = H("...nullifier:v1", configHash, burnTransitionId)` (00 §5). */
79
+ export declare function nullifier(configHashBytes: Uint8Array, burnTransitionIdBytes: Uint8Array): Uint8Array;
80
+ /** One settlement leaf committed by `returnRoot`, in submission order (00 §7). */
81
+ export interface ReturnLeaf {
82
+ readonly nullifier: Uint8Array;
83
+ readonly recipient: Uint8Array;
84
+ readonly amount: bigint;
85
+ readonly feeRecipient: Uint8Array;
86
+ readonly feeAmount: bigint;
87
+ readonly deadline: bigint;
88
+ }
89
+ /** `returnRoot = K( concat of fixed-width abi.encode(leaf) )` (00 §7). */
90
+ export declare function returnRoot(leaves: ReturnLeaf[]): Uint8Array;
91
+ /** One source-lock reference committed by `lockRefRoot` (00 §7). */
92
+ export interface SourceLockRef {
93
+ readonly nonce: bigint;
94
+ readonly digest: Uint8Array;
95
+ }
96
+ /** `lockRefRoot = K( concat of fixed-width abi.encode(ref) )`, sorted by nonce
97
+ * with duplicates rejected (00 §7). */
98
+ export declare function lockRefRoot(refs: SourceLockRef[]): Uint8Array;
99
+ /** The public statement the circuit commits and the vault decodes (00 §7). */
100
+ export interface PublicValues {
101
+ readonly domainTag: Uint8Array;
102
+ readonly configHash: Uint8Array;
103
+ readonly trustBaseHash: Uint8Array;
104
+ readonly spentRootOld: Uint8Array;
105
+ readonly spentRootNew: Uint8Array;
106
+ readonly returnRoot: Uint8Array;
107
+ readonly lockRefRoot: Uint8Array;
108
+ readonly batchSize: number;
109
+ readonly totalAmount: bigint;
110
+ }
111
+ /** `abi.encode(PublicValues)` — the all-static layout the vault abi.decodes (00 §7). */
112
+ export declare function publicValuesAbi(p: PublicValues): Uint8Array;