@utxopia/sdk 0.1.0-alpha.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.
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/package.json +108 -0
- package/packages/btc-client/src/esplora-client.ts +153 -0
- package/packages/btc-client/src/index.ts +3 -0
- package/packages/btc-client/src/op-return.ts +93 -0
- package/packages/btc-client/src/types.ts +112 -0
- package/packages/sdk/README.md +277 -0
- package/packages/sdk/dist/announcement-client.d.ts +64 -0
- package/packages/sdk/dist/announcement-client.js +337 -0
- package/packages/sdk/dist/auditor-ciphertext.d.ts +72 -0
- package/packages/sdk/dist/auditor-ciphertext.js +135 -0
- package/packages/sdk/dist/auditor.d.ts +115 -0
- package/packages/sdk/dist/auditor.js +292 -0
- package/packages/sdk/dist/bitcoin/ika.d.ts +47 -0
- package/packages/sdk/dist/bitcoin/ika.js +74 -0
- package/packages/sdk/dist/bitcoin/index.d.ts +2 -0
- package/packages/sdk/dist/bitcoin/index.js +1 -0
- package/packages/sdk/dist/bound-params.d.ts +96 -0
- package/packages/sdk/dist/bound-params.js +249 -0
- package/packages/sdk/dist/chadbuffer.d.ts +124 -0
- package/packages/sdk/dist/chadbuffer.js +405 -0
- package/packages/sdk/dist/claim-link.d.ts +30 -0
- package/packages/sdk/dist/claim-link.js +50 -0
- package/packages/sdk/dist/client.d.ts +233 -0
- package/packages/sdk/dist/client.js +416 -0
- package/packages/sdk/dist/commitment-tree.d.ts +268 -0
- package/packages/sdk/dist/commitment-tree.js +564 -0
- package/packages/sdk/dist/config.d.ts +177 -0
- package/packages/sdk/dist/config.js +426 -0
- package/packages/sdk/dist/core/esplora.d.ts +110 -0
- package/packages/sdk/dist/core/esplora.js +187 -0
- package/packages/sdk/dist/core/mempool.d.ts +60 -0
- package/packages/sdk/dist/core/mempool.js +107 -0
- package/packages/sdk/dist/crypto-babyjub.d.ts +105 -0
- package/packages/sdk/dist/crypto-babyjub.js +312 -0
- package/packages/sdk/dist/crypto-ed25519.d.ts +116 -0
- package/packages/sdk/dist/crypto-ed25519.js +235 -0
- package/packages/sdk/dist/crypto.d.ts +59 -0
- package/packages/sdk/dist/crypto.js +140 -0
- package/packages/sdk/dist/event-client.d.ts +64 -0
- package/packages/sdk/dist/event-client.js +165 -0
- package/packages/sdk/dist/events.d.ts +127 -0
- package/packages/sdk/dist/events.js +305 -0
- package/packages/sdk/dist/explorer.d.ts +86 -0
- package/packages/sdk/dist/explorer.js +197 -0
- package/packages/sdk/dist/index.d.ts +61 -0
- package/packages/sdk/dist/index.js +216 -0
- package/packages/sdk/dist/instructions.d.ts +1125 -0
- package/packages/sdk/dist/instructions.js +1760 -0
- package/packages/sdk/dist/keys.d.ts +467 -0
- package/packages/sdk/dist/keys.js +799 -0
- package/packages/sdk/dist/logger.d.ts +10 -0
- package/packages/sdk/dist/logger.js +40 -0
- package/packages/sdk/dist/magicblock.d.ts +90 -0
- package/packages/sdk/dist/magicblock.js +164 -0
- package/packages/sdk/dist/merkle.d.ts +82 -0
- package/packages/sdk/dist/merkle.js +141 -0
- package/packages/sdk/dist/note.d.ts +348 -0
- package/packages/sdk/dist/note.js +483 -0
- package/packages/sdk/dist/pda.d.ts +160 -0
- package/packages/sdk/dist/pda.js +384 -0
- package/packages/sdk/dist/pool-state.d.ts +100 -0
- package/packages/sdk/dist/pool-state.js +126 -0
- package/packages/sdk/dist/poseidon.d.ts +53 -0
- package/packages/sdk/dist/poseidon.js +136 -0
- package/packages/sdk/dist/prover/index.d.ts +10 -0
- package/packages/sdk/dist/prover/index.js +10 -0
- package/packages/sdk/dist/prover/mobile.d.ts +45 -0
- package/packages/sdk/dist/prover/mobile.js +227 -0
- package/packages/sdk/dist/prover/web.d.ts +136 -0
- package/packages/sdk/dist/prover/web.js +597 -0
- package/packages/sdk/dist/psbt.d.ts +73 -0
- package/packages/sdk/dist/psbt.js +202 -0
- package/packages/sdk/dist/selective-disclosure.d.ts +181 -0
- package/packages/sdk/dist/selective-disclosure.js +172 -0
- package/packages/sdk/dist/sender-memo.d.ts +149 -0
- package/packages/sdk/dist/sender-memo.js +250 -0
- package/packages/sdk/dist/sns-resolver.d.ts +121 -0
- package/packages/sdk/dist/sns-resolver.js +229 -0
- package/packages/sdk/dist/solana/connection.d.ts +77 -0
- package/packages/sdk/dist/solana/connection.js +133 -0
- package/packages/sdk/dist/solana/priority-fee.d.ts +78 -0
- package/packages/sdk/dist/solana/priority-fee.js +137 -0
- package/packages/sdk/dist/stealth.d.ts +429 -0
- package/packages/sdk/dist/stealth.js +703 -0
- package/packages/sdk/dist/taproot.d.ts +172 -0
- package/packages/sdk/dist/taproot.js +494 -0
- package/packages/sdk/dist/token-registry.d.ts +96 -0
- package/packages/sdk/dist/token-registry.js +122 -0
- package/packages/sdk/dist/utils/encoding.d.ts +19 -0
- package/packages/sdk/dist/utils/encoding.js +29 -0
- package/packages/sdk/dist/vk-registry.d.ts +100 -0
- package/packages/sdk/dist/vk-registry.js +212 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auditor ciphertext — ECDH encrypt-to-auditor for Method-Y permissioned pools.
|
|
3
|
+
*
|
|
4
|
+
* Lets a depositor encrypt note viewing data (tokenId, amount) TO a permissioned-pool
|
|
5
|
+
* auditor's Ed25519 viewing PUBLIC key. Only the auditor's private key can decrypt.
|
|
6
|
+
*
|
|
7
|
+
* Cipher: X25519 ECDH key-agreement + XChaCha20-Poly1305 AEAD.
|
|
8
|
+
*
|
|
9
|
+
* Wire format (112 bytes):
|
|
10
|
+
* eph_pub :: 32 bytes (ephemeral X25519 public key)
|
|
11
|
+
* nonce :: 24 bytes (random XChaCha20 nonce)
|
|
12
|
+
* ciphertext_tag :: 56 bytes (encrypted tokenId(32 BE) || amount(8 LE) + Poly1305 tag(16))
|
|
13
|
+
*/
|
|
14
|
+
export declare const AUDITOR_CIPHERTEXT_BYTES: number;
|
|
15
|
+
export interface AuditorNotePlain {
|
|
16
|
+
tokenId: bigint;
|
|
17
|
+
amount: bigint;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Encrypt note viewing data to an auditor's Ed25519 viewing public key.
|
|
21
|
+
*
|
|
22
|
+
* @param auditorViewingPubKey - 32-byte Ed25519 public key of the auditor
|
|
23
|
+
* @param plain - tokenId + amount to encrypt
|
|
24
|
+
* @param commitment - 32-byte note commitment (AAD — binds blob to note)
|
|
25
|
+
* @param ephemeralPriv - optional 32-byte X25519 private key (random if omitted)
|
|
26
|
+
* @returns 112-byte blob: eph_pub(32) || nonce(24) || ciphertextWithTag(56)
|
|
27
|
+
*/
|
|
28
|
+
export declare function encryptAuditorCiphertext(auditorViewingPubKey: Uint8Array, plain: AuditorNotePlain, commitment: Uint8Array, ephemeralPriv?: Uint8Array): Uint8Array;
|
|
29
|
+
/**
|
|
30
|
+
* Convenience wrapper: build a 112-byte auditor ciphertext blob from note fields.
|
|
31
|
+
*
|
|
32
|
+
* Thin wrapper over {@link encryptAuditorCiphertext} for callers who have the raw
|
|
33
|
+
* note fields (tokenId, amount, commitment) rather than a pre-packed `AuditorNotePlain`.
|
|
34
|
+
*
|
|
35
|
+
* @param input.auditorViewingPubKey - 32-byte Ed25519 public key of the auditor
|
|
36
|
+
* @param input.tokenId - token id (field element, fits in 256 bits)
|
|
37
|
+
* @param input.amount - note amount (≤ 64 bits)
|
|
38
|
+
* @param input.commitment - 32-byte note commitment (binds ciphertext to note)
|
|
39
|
+
* @returns 112-byte blob: eph_pub(32) || nonce(24) || ciphertextWithTag(56)
|
|
40
|
+
*/
|
|
41
|
+
export declare function buildAuditorCiphertextForNote(input: {
|
|
42
|
+
auditorViewingPubKey: Uint8Array;
|
|
43
|
+
tokenId: bigint;
|
|
44
|
+
amount: bigint;
|
|
45
|
+
commitment: Uint8Array;
|
|
46
|
+
}): Uint8Array;
|
|
47
|
+
/**
|
|
48
|
+
* Normalise the `auditorCiphertext` field accepted by permissioned builders.
|
|
49
|
+
*
|
|
50
|
+
* Builders accept either:
|
|
51
|
+
* - a pre-computed `Uint8Array` blob (advanced / off-chain computed), OR
|
|
52
|
+
* - a plain-fields object `{ auditorViewingPubKey, tokenId, amount, commitment }`
|
|
53
|
+
* which will be encrypted on the fly via `buildAuditorCiphertextForNote`.
|
|
54
|
+
*
|
|
55
|
+
* If both forms are supplied the explicit `Uint8Array` wins.
|
|
56
|
+
*/
|
|
57
|
+
export type AuditorCiphertextInput = Uint8Array | {
|
|
58
|
+
auditorViewingPubKey: Uint8Array;
|
|
59
|
+
tokenId: bigint;
|
|
60
|
+
amount: bigint;
|
|
61
|
+
commitment: Uint8Array;
|
|
62
|
+
};
|
|
63
|
+
export declare function resolveAuditorCiphertext(input: AuditorCiphertextInput): Uint8Array;
|
|
64
|
+
/**
|
|
65
|
+
* Decrypt an auditor ciphertext blob using the auditor's Ed25519 viewing private key.
|
|
66
|
+
*
|
|
67
|
+
* @param auditorViewingPrivKey - 32-byte Ed25519 private key of the auditor
|
|
68
|
+
* @param blob - 112-byte blob from `encryptAuditorCiphertext`
|
|
69
|
+
* @param commitment - 32-byte note commitment (must match AAD used during encrypt)
|
|
70
|
+
* @returns decrypted plain, or null on any failure (wrong key, wrong commitment, corrupt)
|
|
71
|
+
*/
|
|
72
|
+
export declare function decryptAuditorCiphertext(auditorViewingPrivKey: Uint8Array, blob: Uint8Array, commitment: Uint8Array): AuditorNotePlain | null;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auditor ciphertext — ECDH encrypt-to-auditor for Method-Y permissioned pools.
|
|
3
|
+
*
|
|
4
|
+
* Lets a depositor encrypt note viewing data (tokenId, amount) TO a permissioned-pool
|
|
5
|
+
* auditor's Ed25519 viewing PUBLIC key. Only the auditor's private key can decrypt.
|
|
6
|
+
*
|
|
7
|
+
* Cipher: X25519 ECDH key-agreement + XChaCha20-Poly1305 AEAD.
|
|
8
|
+
*
|
|
9
|
+
* Wire format (112 bytes):
|
|
10
|
+
* eph_pub :: 32 bytes (ephemeral X25519 public key)
|
|
11
|
+
* nonce :: 24 bytes (random XChaCha20 nonce)
|
|
12
|
+
* ciphertext_tag :: 56 bytes (encrypted tokenId(32 BE) || amount(8 LE) + Poly1305 tag(16))
|
|
13
|
+
*/
|
|
14
|
+
import { xchacha20poly1305 } from "@noble/ciphers/chacha.js";
|
|
15
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
16
|
+
import { x25519 } from "@noble/curves/ed25519.js";
|
|
17
|
+
import { ed25519PubToX25519, ed25519PrivToX25519, x25519PubFromPriv, } from "./crypto-ed25519";
|
|
18
|
+
const DOMAIN = new TextEncoder().encode("utxopia.auditor-ciphertext.v1");
|
|
19
|
+
const EPH_PUB_BYTES = 32;
|
|
20
|
+
const NONCE_BYTES = 24;
|
|
21
|
+
const PLAINTEXT_BYTES = 40; // tokenId(32) + amount(8)
|
|
22
|
+
const TAG_BYTES = 16;
|
|
23
|
+
const CT_WITH_TAG_BYTES = PLAINTEXT_BYTES + TAG_BYTES; // 56
|
|
24
|
+
export const AUDITOR_CIPHERTEXT_BYTES = EPH_PUB_BYTES + NONCE_BYTES + CT_WITH_TAG_BYTES; // 112
|
|
25
|
+
function deriveKey(shared) {
|
|
26
|
+
const buf = new Uint8Array(shared.length + DOMAIN.length);
|
|
27
|
+
buf.set(shared, 0);
|
|
28
|
+
buf.set(DOMAIN, shared.length);
|
|
29
|
+
return sha256(buf);
|
|
30
|
+
}
|
|
31
|
+
function packPlaintext(plain) {
|
|
32
|
+
const out = new Uint8Array(PLAINTEXT_BYTES);
|
|
33
|
+
// tokenId: 32 bytes big-endian
|
|
34
|
+
let t = plain.tokenId;
|
|
35
|
+
for (let i = 31; i >= 0; i--) {
|
|
36
|
+
out[i] = Number(t & 0xffn);
|
|
37
|
+
t >>= 8n;
|
|
38
|
+
}
|
|
39
|
+
if (t !== 0n)
|
|
40
|
+
throw new RangeError("tokenId exceeds 256 bits");
|
|
41
|
+
// amount: 8 bytes little-endian
|
|
42
|
+
let a = plain.amount;
|
|
43
|
+
for (let i = 0; i < 8; i++) {
|
|
44
|
+
out[32 + i] = Number(a & 0xffn);
|
|
45
|
+
a >>= 8n;
|
|
46
|
+
}
|
|
47
|
+
if (a !== 0n)
|
|
48
|
+
throw new RangeError("amount exceeds 64 bits");
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
function unpackPlaintext(bytes) {
|
|
52
|
+
let tokenId = 0n;
|
|
53
|
+
for (let i = 0; i < 32; i++)
|
|
54
|
+
tokenId = (tokenId << 8n) | BigInt(bytes[i]);
|
|
55
|
+
let amount = 0n;
|
|
56
|
+
for (let i = 7; i >= 0; i--)
|
|
57
|
+
amount = (amount << 8n) | BigInt(bytes[32 + i]);
|
|
58
|
+
return { tokenId, amount };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Encrypt note viewing data to an auditor's Ed25519 viewing public key.
|
|
62
|
+
*
|
|
63
|
+
* @param auditorViewingPubKey - 32-byte Ed25519 public key of the auditor
|
|
64
|
+
* @param plain - tokenId + amount to encrypt
|
|
65
|
+
* @param commitment - 32-byte note commitment (AAD — binds blob to note)
|
|
66
|
+
* @param ephemeralPriv - optional 32-byte X25519 private key (random if omitted)
|
|
67
|
+
* @returns 112-byte blob: eph_pub(32) || nonce(24) || ciphertextWithTag(56)
|
|
68
|
+
*/
|
|
69
|
+
export function encryptAuditorCiphertext(auditorViewingPubKey, plain, commitment, ephemeralPriv) {
|
|
70
|
+
if (auditorViewingPubKey.length !== 32)
|
|
71
|
+
throw new Error("auditorViewingPubKey must be 32 bytes");
|
|
72
|
+
if (commitment.length !== 32)
|
|
73
|
+
throw new Error("commitment must be 32 bytes");
|
|
74
|
+
const ephPriv = ephemeralPriv ?? crypto.getRandomValues(new Uint8Array(32));
|
|
75
|
+
const ephPub = x25519PubFromPriv(ephPriv);
|
|
76
|
+
const auditorX = ed25519PubToX25519(auditorViewingPubKey);
|
|
77
|
+
const shared = x25519.getSharedSecret(ephPriv, auditorX);
|
|
78
|
+
const key = deriveKey(shared);
|
|
79
|
+
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_BYTES));
|
|
80
|
+
const aead = xchacha20poly1305(key, nonce, commitment);
|
|
81
|
+
const ciphertextWithTag = aead.encrypt(packPlaintext(plain));
|
|
82
|
+
const blob = new Uint8Array(AUDITOR_CIPHERTEXT_BYTES);
|
|
83
|
+
blob.set(ephPub, 0);
|
|
84
|
+
blob.set(nonce, EPH_PUB_BYTES);
|
|
85
|
+
blob.set(ciphertextWithTag, EPH_PUB_BYTES + NONCE_BYTES);
|
|
86
|
+
return blob;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Convenience wrapper: build a 112-byte auditor ciphertext blob from note fields.
|
|
90
|
+
*
|
|
91
|
+
* Thin wrapper over {@link encryptAuditorCiphertext} for callers who have the raw
|
|
92
|
+
* note fields (tokenId, amount, commitment) rather than a pre-packed `AuditorNotePlain`.
|
|
93
|
+
*
|
|
94
|
+
* @param input.auditorViewingPubKey - 32-byte Ed25519 public key of the auditor
|
|
95
|
+
* @param input.tokenId - token id (field element, fits in 256 bits)
|
|
96
|
+
* @param input.amount - note amount (≤ 64 bits)
|
|
97
|
+
* @param input.commitment - 32-byte note commitment (binds ciphertext to note)
|
|
98
|
+
* @returns 112-byte blob: eph_pub(32) || nonce(24) || ciphertextWithTag(56)
|
|
99
|
+
*/
|
|
100
|
+
export function buildAuditorCiphertextForNote(input) {
|
|
101
|
+
return encryptAuditorCiphertext(input.auditorViewingPubKey, { tokenId: input.tokenId, amount: input.amount }, input.commitment);
|
|
102
|
+
}
|
|
103
|
+
export function resolveAuditorCiphertext(input) {
|
|
104
|
+
if (input instanceof Uint8Array)
|
|
105
|
+
return input;
|
|
106
|
+
return buildAuditorCiphertextForNote(input);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Decrypt an auditor ciphertext blob using the auditor's Ed25519 viewing private key.
|
|
110
|
+
*
|
|
111
|
+
* @param auditorViewingPrivKey - 32-byte Ed25519 private key of the auditor
|
|
112
|
+
* @param blob - 112-byte blob from `encryptAuditorCiphertext`
|
|
113
|
+
* @param commitment - 32-byte note commitment (must match AAD used during encrypt)
|
|
114
|
+
* @returns decrypted plain, or null on any failure (wrong key, wrong commitment, corrupt)
|
|
115
|
+
*/
|
|
116
|
+
export function decryptAuditorCiphertext(auditorViewingPrivKey, blob, commitment) {
|
|
117
|
+
if (blob.length !== AUDITOR_CIPHERTEXT_BYTES)
|
|
118
|
+
return null;
|
|
119
|
+
if (commitment.length !== 32)
|
|
120
|
+
return null;
|
|
121
|
+
const ephPub = blob.slice(0, EPH_PUB_BYTES);
|
|
122
|
+
const nonce = blob.slice(EPH_PUB_BYTES, EPH_PUB_BYTES + NONCE_BYTES);
|
|
123
|
+
const ciphertextWithTag = blob.slice(EPH_PUB_BYTES + NONCE_BYTES);
|
|
124
|
+
try {
|
|
125
|
+
const auditorXPriv = ed25519PrivToX25519(auditorViewingPrivKey);
|
|
126
|
+
const shared = x25519.getSharedSecret(auditorXPriv, ephPub);
|
|
127
|
+
const key = deriveKey(shared);
|
|
128
|
+
const aead = xchacha20poly1305(key, nonce, commitment);
|
|
129
|
+
const plaintext = aead.decrypt(ciphertextWithTag);
|
|
130
|
+
return unpackPlaintext(plaintext);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auditor toolkit (Phase 1)
|
|
3
|
+
*
|
|
4
|
+
* Given a {@link DelegatedViewKey}, produce a structured audit record for
|
|
5
|
+
* every announcement that decrypts cleanly to a positive amount. The scanner:
|
|
6
|
+
*
|
|
7
|
+
* - enforces the delegation's `expiresAt` (wall-clock check)
|
|
8
|
+
* - enforces the delegation's `[fromSlot, toSlot]` honor-system range
|
|
9
|
+
* - honors `ViewPermissions.INCOMING_ONLY` once outgoing memos exist
|
|
10
|
+
* (Phase 2). Today everything coming back is treated as IN since no
|
|
11
|
+
* sender-memo channel has shipped yet.
|
|
12
|
+
*
|
|
13
|
+
* No on-chain enforcement is possible (the auditor already holds the secret);
|
|
14
|
+
* scope is enforced *inside this scanner* so a compliant tool obeys it and the
|
|
15
|
+
* issuer can prove what scope they handed over.
|
|
16
|
+
*/
|
|
17
|
+
import { type OnChainStealthAnnouncement } from "./stealth";
|
|
18
|
+
import { type DelegatedViewKey } from "./keys";
|
|
19
|
+
import { type SenderMemoCiphertext } from "./sender-memo";
|
|
20
|
+
export type { SenderMemoCiphertext };
|
|
21
|
+
/** Direction of a record relative to the audited user. */
|
|
22
|
+
export type AuditDirection = "IN" | "OUT" | "SELF" | "UNKNOWN" | "AUDITOR_VISIBLE";
|
|
23
|
+
/** A single matched announcement, ready for report rendering. */
|
|
24
|
+
export interface AuditRecord {
|
|
25
|
+
slot: number;
|
|
26
|
+
blockTime: number;
|
|
27
|
+
leafIndex: number;
|
|
28
|
+
direction: AuditDirection;
|
|
29
|
+
announcementType: number;
|
|
30
|
+
tokenId: bigint;
|
|
31
|
+
amount: bigint;
|
|
32
|
+
commitmentHex: string;
|
|
33
|
+
ephemeralPubHex: string;
|
|
34
|
+
}
|
|
35
|
+
export interface AuditScanOptions {
|
|
36
|
+
/** Token IDs to scan for. Required — auditor must know which token namespaces to look in. */
|
|
37
|
+
tokenIds: bigint[];
|
|
38
|
+
/** Override slot range (otherwise uses key's fromSlot/toSlot). */
|
|
39
|
+
fromSlot?: number;
|
|
40
|
+
toSlot?: number;
|
|
41
|
+
/** Clock for `expiresAt` check. Override for tests; defaults to `Date.now()`. */
|
|
42
|
+
now?: () => number;
|
|
43
|
+
/** Sender-memo events (Phase 2). When supplied, OUT records will be produced. */
|
|
44
|
+
senderMemos?: ReadonlyArray<OnChainSenderMemo>;
|
|
45
|
+
/** Auditor ciphertext events (Method-Y). When supplied, AUDITOR_VISIBLE records will be produced. */
|
|
46
|
+
auditorCiphertexts?: ReadonlyArray<{
|
|
47
|
+
commitment: Uint8Array;
|
|
48
|
+
blob: Uint8Array;
|
|
49
|
+
slot?: number;
|
|
50
|
+
blockTime?: number;
|
|
51
|
+
}>;
|
|
52
|
+
/** Auditor viewing private key (32-byte Ed25519). Required to decrypt auditorCiphertexts. */
|
|
53
|
+
auditorViewingPrivKey?: Uint8Array;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Sender-memo event as observed on-chain. Emitted by `emit_sender_memo` in the
|
|
57
|
+
* UTXOpia program. The memo carries its own `commitment` and `leafIndex` so
|
|
58
|
+
* the auditor can join it back to the corresponding tree leaf, even though
|
|
59
|
+
* the memo itself isn't a leaf.
|
|
60
|
+
*/
|
|
61
|
+
export interface OnChainSenderMemo extends SenderMemoCiphertext {
|
|
62
|
+
blockTime?: number;
|
|
63
|
+
slot?: number;
|
|
64
|
+
}
|
|
65
|
+
export interface AuditScanSummary {
|
|
66
|
+
records: AuditRecord[];
|
|
67
|
+
/** How many announcements fell outside the slot range. */
|
|
68
|
+
outOfRangeSkipped: number;
|
|
69
|
+
/** How many announcements lacked a slot but the key required one. */
|
|
70
|
+
unscopedSkipped: number;
|
|
71
|
+
/** How many announcements decrypted to something invalid (wrong key, out of range amount). */
|
|
72
|
+
notForViewerSkipped: number;
|
|
73
|
+
/** Effective slot range that was actually enforced. */
|
|
74
|
+
effectiveFromSlot?: number;
|
|
75
|
+
effectiveToSlot?: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Annotated announcement input — carries the same shape as
|
|
79
|
+
* {@link OnChainStealthAnnouncement} but the `tokenId` is opt-in. When
|
|
80
|
+
* omitted, the caller will be matched against every `tokenIds` entry.
|
|
81
|
+
*/
|
|
82
|
+
export interface AuditScanAnnouncement extends OnChainStealthAnnouncement {
|
|
83
|
+
/** Optional — when present, scanner only tries the matching tokenId. */
|
|
84
|
+
tokenId?: bigint;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Scan announcements with a delegated viewing key and produce {@link AuditRecord}s.
|
|
88
|
+
*
|
|
89
|
+
* Throws if the key is expired or lacks `SCAN` permission, or if the key is
|
|
90
|
+
* missing the spending-pub/nullifying-key material required to verify deposits.
|
|
91
|
+
*/
|
|
92
|
+
export declare function auditScan(key: DelegatedViewKey, announcements: ReadonlyArray<AuditScanAnnouncement>, options: AuditScanOptions): Promise<AuditScanSummary>;
|
|
93
|
+
/**
|
|
94
|
+
* Scan auditor ciphertexts (Method-Y) using only an auditor viewing private key.
|
|
95
|
+
*
|
|
96
|
+
* Unlike {@link auditScan}, this function does NOT require a full {@link DelegatedViewKey}
|
|
97
|
+
* with spendingPubKey/nullifyingKey — it only needs the auditor's 32-byte Ed25519
|
|
98
|
+
* viewing private key. This is the primary entry point for Method-Y auditor use cases.
|
|
99
|
+
*/
|
|
100
|
+
export declare function auditScanCiphertexts(auditorViewingPrivKey: Uint8Array, ciphertexts: ReadonlyArray<{
|
|
101
|
+
commitment: Uint8Array;
|
|
102
|
+
blob: Uint8Array;
|
|
103
|
+
slot?: number;
|
|
104
|
+
blockTime?: number;
|
|
105
|
+
}>, options?: {
|
|
106
|
+
tokenIds?: bigint[];
|
|
107
|
+
fromSlot?: number;
|
|
108
|
+
toSlot?: number;
|
|
109
|
+
}): Promise<AuditScanSummary>;
|
|
110
|
+
/**
|
|
111
|
+
* Render audit records as CSV. Always returns a single trailing newline.
|
|
112
|
+
*
|
|
113
|
+
* Columns: slot, block_time_iso, leaf_index, direction, type, token_id, amount, commitment, ephemeral_pub
|
|
114
|
+
*/
|
|
115
|
+
export declare function auditRecordsToCsv(records: ReadonlyArray<AuditRecord>): string;
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auditor toolkit (Phase 1)
|
|
3
|
+
*
|
|
4
|
+
* Given a {@link DelegatedViewKey}, produce a structured audit record for
|
|
5
|
+
* every announcement that decrypts cleanly to a positive amount. The scanner:
|
|
6
|
+
*
|
|
7
|
+
* - enforces the delegation's `expiresAt` (wall-clock check)
|
|
8
|
+
* - enforces the delegation's `[fromSlot, toSlot]` honor-system range
|
|
9
|
+
* - honors `ViewPermissions.INCOMING_ONLY` once outgoing memos exist
|
|
10
|
+
* (Phase 2). Today everything coming back is treated as IN since no
|
|
11
|
+
* sender-memo channel has shipped yet.
|
|
12
|
+
*
|
|
13
|
+
* No on-chain enforcement is possible (the auditor already holds the secret);
|
|
14
|
+
* scope is enforced *inside this scanner* so a compliant tool obeys it and the
|
|
15
|
+
* issuer can prove what scope they handed over.
|
|
16
|
+
*/
|
|
17
|
+
import { scanAnnouncementsViewOnly, ANNOUNCEMENT_TYPE_DEPOSIT, } from "./stealth";
|
|
18
|
+
import { babyJubDecompress, bytesToHex } from "./crypto";
|
|
19
|
+
import { ViewPermissions, hasPermission, isDelegatedKeyValid, isSlotInDelegatedRange, } from "./keys";
|
|
20
|
+
import { decryptSenderMemo } from "./sender-memo";
|
|
21
|
+
import { decryptAuditorCiphertext } from "./auditor-ciphertext";
|
|
22
|
+
// Range intersection: undefined means unbounded on that side.
|
|
23
|
+
function intersectLower(a, b) {
|
|
24
|
+
if (a == null)
|
|
25
|
+
return b;
|
|
26
|
+
if (b == null)
|
|
27
|
+
return a;
|
|
28
|
+
return Math.max(a, b);
|
|
29
|
+
}
|
|
30
|
+
function intersectUpper(a, b) {
|
|
31
|
+
if (a == null)
|
|
32
|
+
return b;
|
|
33
|
+
if (b == null)
|
|
34
|
+
return a;
|
|
35
|
+
return Math.min(a, b);
|
|
36
|
+
}
|
|
37
|
+
function buildAuditorVisibleRecord(plain, commitment, slot, blockTime) {
|
|
38
|
+
return {
|
|
39
|
+
slot,
|
|
40
|
+
blockTime,
|
|
41
|
+
leafIndex: -1,
|
|
42
|
+
direction: "AUDITOR_VISIBLE",
|
|
43
|
+
announcementType: -1,
|
|
44
|
+
tokenId: plain.tokenId,
|
|
45
|
+
amount: plain.amount,
|
|
46
|
+
commitmentHex: bytesToHex(commitment),
|
|
47
|
+
ephemeralPubHex: "",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Scan announcements with a delegated viewing key and produce {@link AuditRecord}s.
|
|
52
|
+
*
|
|
53
|
+
* Throws if the key is expired or lacks `SCAN` permission, or if the key is
|
|
54
|
+
* missing the spending-pub/nullifying-key material required to verify deposits.
|
|
55
|
+
*/
|
|
56
|
+
export async function auditScan(key, announcements, options) {
|
|
57
|
+
if (!hasPermission(key, ViewPermissions.SCAN)) {
|
|
58
|
+
throw new Error("Delegated key missing SCAN permission");
|
|
59
|
+
}
|
|
60
|
+
if (!isDelegatedKeyValid(key)) {
|
|
61
|
+
throw new Error("Delegated key has expired");
|
|
62
|
+
}
|
|
63
|
+
if (!key.spendingPubKeyCompressed || key.nullifyingKey == null) {
|
|
64
|
+
throw new Error("Delegated key missing spendingPubKey/nullifyingKey — re-export with a v2 key");
|
|
65
|
+
}
|
|
66
|
+
if (options.tokenIds.length === 0) {
|
|
67
|
+
throw new Error("auditScan requires at least one tokenId");
|
|
68
|
+
}
|
|
69
|
+
// Intersect the CLI range with the key's grant — options may only narrow the
|
|
70
|
+
// delegated [fromSlot, toSlot], never widen it (undefined = unbounded that side).
|
|
71
|
+
const effectiveFromSlot = intersectLower(options.fromSlot, key.fromSlot);
|
|
72
|
+
const effectiveToSlot = intersectUpper(options.toSlot, key.toSlot);
|
|
73
|
+
const keyForRange = {
|
|
74
|
+
...key,
|
|
75
|
+
fromSlot: effectiveFromSlot,
|
|
76
|
+
toSlot: effectiveToSlot,
|
|
77
|
+
};
|
|
78
|
+
let outOfRangeSkipped = 0;
|
|
79
|
+
let unscopedSkipped = 0;
|
|
80
|
+
const inScope = [];
|
|
81
|
+
for (const ann of announcements) {
|
|
82
|
+
if (effectiveFromSlot != null || effectiveToSlot != null) {
|
|
83
|
+
if (ann.slot == null) {
|
|
84
|
+
unscopedSkipped++;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (!isSlotInDelegatedRange(keyForRange, ann.slot)) {
|
|
88
|
+
outOfRangeSkipped++;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
inScope.push(ann);
|
|
93
|
+
}
|
|
94
|
+
const viewOnly = {
|
|
95
|
+
viewingPrivKey: key.viewingPrivKey,
|
|
96
|
+
spendingPubKey: babyJubDecompress(key.spendingPubKeyCompressed),
|
|
97
|
+
nullifyingKey: key.nullifyingKey,
|
|
98
|
+
};
|
|
99
|
+
// Honor INCOMING_ONLY: when set, skip OUT records derived from sender memos.
|
|
100
|
+
// The auditor still scans incoming announcements; just doesn't produce
|
|
101
|
+
// outgoing-direction rows. Enforcement is honor-system (the key holder could
|
|
102
|
+
// run auditScan locally with this branch removed), but the issuance contract
|
|
103
|
+
// is that an INCOMING_ONLY delegation produces no OUT records under the
|
|
104
|
+
// canonical SDK path.
|
|
105
|
+
const incomingOnly = hasPermission(key, ViewPermissions.INCOMING_ONLY);
|
|
106
|
+
const records = [];
|
|
107
|
+
const seenLeafIndex = new Set();
|
|
108
|
+
// Index inScope by leafIndex so we can recover slot after the inner scan,
|
|
109
|
+
// which strips the slot field from its output.
|
|
110
|
+
const slotByLeafIndex = new Map();
|
|
111
|
+
for (const ann of inScope)
|
|
112
|
+
slotByLeafIndex.set(ann.leafIndex, ann.slot);
|
|
113
|
+
for (const tokenId of options.tokenIds) {
|
|
114
|
+
// Either the announcement explicitly targets this token, or its tokenId is
|
|
115
|
+
// unknown and we try every requested token id (commitment match disambiguates).
|
|
116
|
+
const subset = inScope.filter((a) => a.tokenId == null || a.tokenId === tokenId);
|
|
117
|
+
if (subset.length === 0)
|
|
118
|
+
continue;
|
|
119
|
+
const matched = await scanAnnouncementsViewOnly(viewOnly, subset, tokenId);
|
|
120
|
+
for (const m of matched) {
|
|
121
|
+
if (seenLeafIndex.has(m.leafIndex))
|
|
122
|
+
continue;
|
|
123
|
+
seenLeafIndex.add(m.leafIndex);
|
|
124
|
+
records.push({
|
|
125
|
+
slot: slotByLeafIndex.get(m.leafIndex) ?? 0,
|
|
126
|
+
blockTime: m.blockTime ?? 0,
|
|
127
|
+
leafIndex: m.leafIndex,
|
|
128
|
+
direction: "IN",
|
|
129
|
+
announcementType: subset.find((a) => a.leafIndex === m.leafIndex)?.announcementType ??
|
|
130
|
+
ANNOUNCEMENT_TYPE_DEPOSIT,
|
|
131
|
+
tokenId,
|
|
132
|
+
amount: m.amount,
|
|
133
|
+
commitmentHex: bytesToHex(m.commitment),
|
|
134
|
+
ephemeralPubHex: bytesToHex(m.ephemeralPub),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const notForViewerSkipped = inScope.length - seenLeafIndex.size;
|
|
139
|
+
// Sender memos (Phase 2): produce OUT records the user emitted.
|
|
140
|
+
// Skipped entirely when the delegation is INCOMING_ONLY.
|
|
141
|
+
if (!incomingOnly && options.senderMemos && options.senderMemos.length > 0) {
|
|
142
|
+
for (const memo of options.senderMemos) {
|
|
143
|
+
if (effectiveFromSlot != null || effectiveToSlot != null) {
|
|
144
|
+
if (memo.slot == null) {
|
|
145
|
+
unscopedSkipped++;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (!isSlotInDelegatedRange(keyForRange, memo.slot)) {
|
|
149
|
+
outOfRangeSkipped++;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const plain = decryptSenderMemo(key.viewingPrivKey, memo);
|
|
154
|
+
if (!plain)
|
|
155
|
+
continue;
|
|
156
|
+
// Sender memos are encrypted to the user's own viewing key, so the
|
|
157
|
+
// primary sanity check is non-zero. The 8-byte field already caps amount at u64::MAX.
|
|
158
|
+
if (plain.amount <= 0n)
|
|
159
|
+
continue;
|
|
160
|
+
// Token filter: respect the requested tokenIds set.
|
|
161
|
+
if (!options.tokenIds.some((t) => t === plain.tokenId))
|
|
162
|
+
continue;
|
|
163
|
+
records.push({
|
|
164
|
+
slot: memo.slot ?? 0,
|
|
165
|
+
blockTime: memo.blockTime ?? 0,
|
|
166
|
+
leafIndex: memo.leafIndex,
|
|
167
|
+
direction: "OUT",
|
|
168
|
+
announcementType: -1, // distinct from deposit (0) / transfer (1)
|
|
169
|
+
tokenId: plain.tokenId,
|
|
170
|
+
amount: plain.amount,
|
|
171
|
+
commitmentHex: bytesToHex(memo.commitment),
|
|
172
|
+
ephemeralPubHex: "",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Auditor ciphertexts (Method-Y): produce AUDITOR_VISIBLE records.
|
|
177
|
+
if (options.auditorViewingPrivKey &&
|
|
178
|
+
options.auditorCiphertexts &&
|
|
179
|
+
options.auditorCiphertexts.length > 0) {
|
|
180
|
+
for (const entry of options.auditorCiphertexts) {
|
|
181
|
+
if (effectiveFromSlot != null || effectiveToSlot != null) {
|
|
182
|
+
if (entry.slot == null) {
|
|
183
|
+
unscopedSkipped++;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (!isSlotInDelegatedRange(keyForRange, entry.slot)) {
|
|
187
|
+
outOfRangeSkipped++;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const plain = decryptAuditorCiphertext(options.auditorViewingPrivKey, entry.blob, entry.commitment);
|
|
192
|
+
if (!plain)
|
|
193
|
+
continue;
|
|
194
|
+
if (plain.amount <= 0n)
|
|
195
|
+
continue;
|
|
196
|
+
if (!options.tokenIds.some((t) => t === plain.tokenId))
|
|
197
|
+
continue;
|
|
198
|
+
records.push(buildAuditorVisibleRecord(plain, entry.commitment, entry.slot ?? 0, entry.blockTime ?? 0));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
records,
|
|
203
|
+
outOfRangeSkipped,
|
|
204
|
+
unscopedSkipped,
|
|
205
|
+
notForViewerSkipped: Math.max(0, notForViewerSkipped),
|
|
206
|
+
effectiveFromSlot,
|
|
207
|
+
effectiveToSlot,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Scan auditor ciphertexts (Method-Y) using only an auditor viewing private key.
|
|
212
|
+
*
|
|
213
|
+
* Unlike {@link auditScan}, this function does NOT require a full {@link DelegatedViewKey}
|
|
214
|
+
* with spendingPubKey/nullifyingKey — it only needs the auditor's 32-byte Ed25519
|
|
215
|
+
* viewing private key. This is the primary entry point for Method-Y auditor use cases.
|
|
216
|
+
*/
|
|
217
|
+
export async function auditScanCiphertexts(auditorViewingPrivKey, ciphertexts, options) {
|
|
218
|
+
const records = [];
|
|
219
|
+
let outOfRangeSkipped = 0;
|
|
220
|
+
let unscopedSkipped = 0;
|
|
221
|
+
let notForViewerSkipped = 0;
|
|
222
|
+
const effectiveFromSlot = options?.fromSlot;
|
|
223
|
+
const effectiveToSlot = options?.toSlot;
|
|
224
|
+
for (const entry of ciphertexts) {
|
|
225
|
+
if (effectiveFromSlot != null || effectiveToSlot != null) {
|
|
226
|
+
if (entry.slot == null) {
|
|
227
|
+
unscopedSkipped++;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (effectiveFromSlot != null && entry.slot < effectiveFromSlot) {
|
|
231
|
+
outOfRangeSkipped++;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (effectiveToSlot != null && entry.slot > effectiveToSlot) {
|
|
235
|
+
outOfRangeSkipped++;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const plain = decryptAuditorCiphertext(auditorViewingPrivKey, entry.blob, entry.commitment);
|
|
240
|
+
if (!plain) {
|
|
241
|
+
notForViewerSkipped++;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (plain.amount <= 0n) {
|
|
245
|
+
notForViewerSkipped++;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (options?.tokenIds && options.tokenIds.length > 0 && !options.tokenIds.some((t) => t === plain.tokenId)) {
|
|
249
|
+
notForViewerSkipped++;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
records.push(buildAuditorVisibleRecord(plain, entry.commitment, entry.slot ?? 0, entry.blockTime ?? 0));
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
records,
|
|
256
|
+
outOfRangeSkipped,
|
|
257
|
+
unscopedSkipped,
|
|
258
|
+
notForViewerSkipped,
|
|
259
|
+
effectiveFromSlot,
|
|
260
|
+
effectiveToSlot,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Render audit records as CSV. Always returns a single trailing newline.
|
|
265
|
+
*
|
|
266
|
+
* Columns: slot, block_time_iso, leaf_index, direction, type, token_id, amount, commitment, ephemeral_pub
|
|
267
|
+
*/
|
|
268
|
+
export function auditRecordsToCsv(records) {
|
|
269
|
+
const header = "slot,block_time_iso,leaf_index,direction,type,token_id,amount,commitment,ephemeral_pub";
|
|
270
|
+
const lines = records.map((r) => {
|
|
271
|
+
const ts = r.blockTime > 0 ? new Date(r.blockTime * 1000).toISOString() : "";
|
|
272
|
+
const typeLabel = r.direction === "OUT"
|
|
273
|
+
? "sender-memo"
|
|
274
|
+
: r.direction === "AUDITOR_VISIBLE"
|
|
275
|
+
? "auditor-ciphertext"
|
|
276
|
+
: r.announcementType === ANNOUNCEMENT_TYPE_DEPOSIT
|
|
277
|
+
? "deposit"
|
|
278
|
+
: "transfer";
|
|
279
|
+
return [
|
|
280
|
+
r.slot,
|
|
281
|
+
ts,
|
|
282
|
+
r.leafIndex,
|
|
283
|
+
r.direction,
|
|
284
|
+
typeLabel,
|
|
285
|
+
r.tokenId.toString(),
|
|
286
|
+
r.amount.toString(),
|
|
287
|
+
r.commitmentHex,
|
|
288
|
+
r.ephemeralPubHex,
|
|
289
|
+
].join(",");
|
|
290
|
+
});
|
|
291
|
+
return [header, ...lines].join("\n") + "\n";
|
|
292
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ika dWallet → Bitcoin P2TR address derivation.
|
|
3
|
+
*
|
|
4
|
+
* UTXOpia v2 custody is held by an Ika dWallet on Solana. The dWallet's
|
|
5
|
+
* compressed secp256k1 public key (or its x-only form) identifies the Bitcoin
|
|
6
|
+
* key that Ika can sign for. Ika pre-alpha signs for the raw x-only key; it
|
|
7
|
+
* does not currently produce signatures for UTXOpia's per-deposit tweaked keys.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Reference to an Ika dWallet for address derivation.
|
|
11
|
+
*
|
|
12
|
+
* `literal-pubkey` is the synchronous form — pass a 33-byte compressed or
|
|
13
|
+
* 32-byte x-only pubkey directly. The `id` form is async and must be resolved
|
|
14
|
+
* to a pubkey via the Ika SDK before reaching this helper.
|
|
15
|
+
*/
|
|
16
|
+
export type IkaDWalletRef = {
|
|
17
|
+
type: "literal-xonly";
|
|
18
|
+
xonlyPubkey: Uint8Array;
|
|
19
|
+
} | {
|
|
20
|
+
type: "literal-compressed";
|
|
21
|
+
compressedPubkey: Uint8Array;
|
|
22
|
+
} | {
|
|
23
|
+
type: "id";
|
|
24
|
+
dwalletId: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Derive the BIP-341 P2TR (key-path-only) address controlled by an Ika dWallet.
|
|
28
|
+
*
|
|
29
|
+
* BIP-341 (no script tree):
|
|
30
|
+
* t = h_tapTweak(P)
|
|
31
|
+
* Q = P + t*G
|
|
32
|
+
* output_key = x(Q)
|
|
33
|
+
* address = bech32m(hrp, [witness_version=1, ...words(output_key)])
|
|
34
|
+
*
|
|
35
|
+
* @param ref The Ika dWallet reference (literal pubkey, or future async id).
|
|
36
|
+
* @param network "mainnet" | "testnet" | "regtest"
|
|
37
|
+
* @returns The P2TR address (`bc1p…` / `tb1p…` / `bcrt1p…`)
|
|
38
|
+
*/
|
|
39
|
+
export declare function deriveCustodyAddressFromIkaDWallet(ref: IkaDWalletRef, network: "mainnet" | "testnet" | "regtest"): string;
|
|
40
|
+
/**
|
|
41
|
+
* Encode a raw x-only public key as a P2TR witness program.
|
|
42
|
+
*
|
|
43
|
+
* This intentionally does not apply BIP-341's no-script TapTweak. It is the
|
|
44
|
+
* direct-vault address used by the current Ika pre-alpha mock signer, because
|
|
45
|
+
* the signer returns Schnorr signatures for the dWallet's raw x-only key.
|
|
46
|
+
*/
|
|
47
|
+
export declare function deriveRawXOnlyP2TRAddress(xonlyPubkey: Uint8Array, network: "mainnet" | "testnet" | "regtest"): string;
|