@orangecheck/sdk 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/dist/attestation.d.mts +1 -1
- package/dist/attestation.d.ts +1 -1
- package/dist/canonical.d.mts +1 -1
- package/dist/canonical.d.ts +1 -1
- package/dist/challenge.d.mts +1 -1
- package/dist/challenge.d.ts +1 -1
- package/dist/challenge.js +10 -35
- package/dist/challenge.js.map +1 -1
- package/dist/challenge.mjs +10 -15
- package/dist/challenge.mjs.map +1 -1
- package/dist/identity.d.mts +1 -1
- package/dist/identity.d.ts +1 -1
- package/dist/index.d.mts +105 -3
- package/dist/index.d.ts +105 -3
- package/dist/index.js +269 -112
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +256 -92
- package/dist/index.mjs.map +1 -1
- package/dist/nostr.d.mts +1 -1
- package/dist/nostr.d.ts +1 -1
- package/dist/{types-D9Tb1roj.d.mts → types-BBRMCgMo.d.mts} +1 -0
- package/dist/{types-D9Tb1roj.d.ts → types-BBRMCgMo.d.ts} +1 -0
- package/dist/verify.d.mts +1 -1
- package/dist/verify.d.ts +1 -1
- package/dist/verify.js +85 -96
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +85 -76
- package/dist/verify.mjs.map +1 -1
- package/package.json +2 -4
package/dist/index.js
CHANGED
|
@@ -2,30 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
var base = require('@scure/base');
|
|
4
4
|
var buffer = require('buffer');
|
|
5
|
-
var
|
|
6
|
-
var bitcoinMessage = require('bitcoinjs-message');
|
|
7
|
-
var secp256k1_js = require('@noble/curves/secp256k1.js');
|
|
5
|
+
var bip322 = require('@orangecheck/bip322');
|
|
8
6
|
var sha2_js = require('@noble/hashes/sha2.js');
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (e && e.__esModule) return e;
|
|
12
|
-
var n = Object.create(null);
|
|
13
|
-
if (e) {
|
|
14
|
-
Object.keys(e).forEach(function (k) {
|
|
15
|
-
if (k !== 'default') {
|
|
16
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
17
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
18
|
-
enumerable: true,
|
|
19
|
-
get: function () { return e[k]; }
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
n.default = e;
|
|
25
|
-
return Object.freeze(n);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
var bitcoinMessage__namespace = /*#__PURE__*/_interopNamespace(bitcoinMessage);
|
|
7
|
+
var utils_js = require('@noble/hashes/utils.js');
|
|
8
|
+
var secp256k1_js = require('@noble/curves/secp256k1.js');
|
|
29
9
|
|
|
30
10
|
// src/canonical.ts
|
|
31
11
|
function toLowerHex(bytes) {
|
|
@@ -883,87 +863,79 @@ async function verifySignature({
|
|
|
883
863
|
sig,
|
|
884
864
|
scheme
|
|
885
865
|
}) {
|
|
886
|
-
|
|
887
|
-
|
|
866
|
+
const detectedScheme = scheme || detectSignatureScheme(addr, sig);
|
|
867
|
+
log4.debug(
|
|
868
|
+
{ addr, msgLength: msg.length, sigLength: sig.length, scheme: detectedScheme },
|
|
869
|
+
"Verifying signature"
|
|
870
|
+
);
|
|
871
|
+
if (detectedScheme === "bip322") {
|
|
872
|
+
const sigBase64 = toBase64Maybe(sig);
|
|
888
873
|
log4.debug(
|
|
889
|
-
{
|
|
890
|
-
"
|
|
874
|
+
{ sigBase64: sigBase64.substring(0, 50) + "..." },
|
|
875
|
+
"Signature converted to base64"
|
|
891
876
|
);
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
if (isSegWit && sigBase64.length === 88) {
|
|
912
|
-
log4.warn(
|
|
913
|
-
{ addr, wallet: "possibly Electrum", sigLength: sigBase64.length },
|
|
914
|
-
"BIP-322 verification failed - likely Electrum wallet (does not support BIP-322 for SegWit)"
|
|
915
|
-
);
|
|
916
|
-
} else {
|
|
917
|
-
log4.error(
|
|
918
|
-
{
|
|
919
|
-
addr,
|
|
920
|
-
sigLength: sigBase64.length,
|
|
921
|
-
msgLength: msg.length,
|
|
922
|
-
msgPreview: msg.substring(0, 200)
|
|
923
|
-
},
|
|
924
|
-
"BIP-322 verification failed - signature does not match message"
|
|
925
|
-
);
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
return ok ? "sig_ok_bip322" : "sig_invalid";
|
|
929
|
-
}
|
|
930
|
-
if (detectedScheme === "legacy") {
|
|
931
|
-
const sigBase64 = toBase64Maybe(sig);
|
|
932
|
-
if (!addr.startsWith("1") && !addr.startsWith("m") && !addr.startsWith("n")) {
|
|
933
|
-
log4.warn({ addr }, "Legacy signature attempted on non-P2PKH address");
|
|
934
|
-
return "sig_unsupported_script";
|
|
935
|
-
}
|
|
936
|
-
log4.debug(
|
|
937
|
-
{
|
|
938
|
-
addr,
|
|
939
|
-
sigBase64: sigBase64.substring(0, 50) + "...",
|
|
940
|
-
msgPreview: msg.substring(0, 100) + "...",
|
|
941
|
-
msgLength: msg.length
|
|
942
|
-
},
|
|
943
|
-
"Verifying legacy signature"
|
|
944
|
-
);
|
|
945
|
-
const ok = bitcoinMessage__namespace.verify(msg, addr, sigBase64);
|
|
946
|
-
log4.info({ addr, ok, sigBase64Length: sigBase64.length }, "Legacy verification result");
|
|
947
|
-
if (!ok) {
|
|
877
|
+
log4.debug(
|
|
878
|
+
{
|
|
879
|
+
msgFull: msg,
|
|
880
|
+
msgLines: msg.split("\n").length,
|
|
881
|
+
msgFirstLine: msg.split("\n")[0],
|
|
882
|
+
msgLastLine: msg.split("\n")[msg.split("\n").length - 1]
|
|
883
|
+
},
|
|
884
|
+
"Message being verified"
|
|
885
|
+
);
|
|
886
|
+
const ok = bip322.verifySignature(addr, msg, sigBase64);
|
|
887
|
+
log4.info({ addr, ok }, "BIP-322 verification result");
|
|
888
|
+
if (!ok) {
|
|
889
|
+
const isSegWit = addr.startsWith("bc1") || addr.startsWith("tb1");
|
|
890
|
+
if (isSegWit && sigBase64.length === 88) {
|
|
891
|
+
log4.warn(
|
|
892
|
+
{ addr, wallet: "possibly Electrum", sigLength: sigBase64.length },
|
|
893
|
+
"BIP-322 verification failed - likely Electrum wallet (does not support BIP-322 for SegWit)"
|
|
894
|
+
);
|
|
895
|
+
} else {
|
|
948
896
|
log4.error(
|
|
949
897
|
{
|
|
950
898
|
addr,
|
|
951
|
-
|
|
952
|
-
|
|
899
|
+
sigLength: sigBase64.length,
|
|
900
|
+
msgLength: msg.length,
|
|
901
|
+
msgPreview: msg.substring(0, 200)
|
|
953
902
|
},
|
|
954
|
-
"
|
|
903
|
+
"BIP-322 verification failed - signature does not match message"
|
|
955
904
|
);
|
|
956
905
|
}
|
|
957
|
-
return ok ? "sig_ok_legacy" : "sig_invalid";
|
|
958
906
|
}
|
|
959
|
-
return "
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
907
|
+
return ok ? "sig_ok_bip322" : "sig_invalid";
|
|
908
|
+
}
|
|
909
|
+
if (detectedScheme === "legacy") {
|
|
910
|
+
const sigBase64 = toBase64Maybe(sig);
|
|
911
|
+
if (!addr.startsWith("1") && !addr.startsWith("m") && !addr.startsWith("n")) {
|
|
912
|
+
log4.warn({ addr }, "Legacy signature attempted on non-P2PKH address");
|
|
913
|
+
return "sig_unsupported_script";
|
|
914
|
+
}
|
|
915
|
+
log4.debug(
|
|
916
|
+
{
|
|
917
|
+
addr,
|
|
918
|
+
sigBase64: sigBase64.substring(0, 50) + "...",
|
|
919
|
+
msgPreview: msg.substring(0, 100) + "...",
|
|
920
|
+
msgLength: msg.length
|
|
921
|
+
},
|
|
922
|
+
"Verifying legacy signature"
|
|
964
923
|
);
|
|
965
|
-
|
|
924
|
+
const ok = bip322.verifySignature(addr, msg, sigBase64);
|
|
925
|
+
log4.info({ addr, ok, sigBase64Length: sigBase64.length }, "Legacy verification result");
|
|
926
|
+
if (!ok) {
|
|
927
|
+
log4.error(
|
|
928
|
+
{
|
|
929
|
+
addr,
|
|
930
|
+
msgFirstLine: msg.split("\n")[0],
|
|
931
|
+
sigFormat: sigBase64.substring(0, 10)
|
|
932
|
+
},
|
|
933
|
+
"Legacy signature verification failed - check message format and signature"
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
return ok ? "sig_ok_legacy" : "sig_invalid";
|
|
966
937
|
}
|
|
938
|
+
return "invalid_scheme";
|
|
967
939
|
}
|
|
968
940
|
function parseCanonicalMessage(msg) {
|
|
969
941
|
const lines = msg.split("\n").map((l) => l.trim());
|
|
@@ -1055,7 +1027,7 @@ async function getAddressUtxos(address, network, opts) {
|
|
|
1055
1027
|
log4.error({ endpoints, error: lastError?.message, network }, "All Esplora endpoints failed");
|
|
1056
1028
|
throw new Error(`esplora_all_failed: ${lastError?.message || "unknown"}`);
|
|
1057
1029
|
}
|
|
1058
|
-
function computeMetrics(utxos, bondAmount) {
|
|
1030
|
+
function computeMetrics(utxos, bondAmount, coverSats) {
|
|
1059
1031
|
const confirmedUtxos = utxos.filter((u) => u.status.confirmed);
|
|
1060
1032
|
if (confirmedUtxos.length === 0) {
|
|
1061
1033
|
return { sats_bonded: 0, days_unspent: 0, confirmed_balance: 0 };
|
|
@@ -1088,13 +1060,30 @@ function computeMetrics(utxos, bondAmount) {
|
|
|
1088
1060
|
};
|
|
1089
1061
|
}
|
|
1090
1062
|
const sats_bonded = confirmed_balance;
|
|
1063
|
+
if (coverSats !== void 0 && coverSats > 0) {
|
|
1064
|
+
const sorted = [...confirmedUtxos].sort(
|
|
1065
|
+
(a, b) => (a.status.block_height ?? Infinity) - (b.status.block_height ?? Infinity)
|
|
1066
|
+
);
|
|
1067
|
+
let sum = 0;
|
|
1068
|
+
let youngestTime = 0;
|
|
1069
|
+
for (const utxo of sorted) {
|
|
1070
|
+
sum += utxo.value;
|
|
1071
|
+
youngestTime = Math.max(youngestTime, utxo.status.block_time || Date.now() / 1e3);
|
|
1072
|
+
if (sum >= coverSats) break;
|
|
1073
|
+
}
|
|
1074
|
+
const days_unspent2 = Math.max(
|
|
1075
|
+
0,
|
|
1076
|
+
Math.floor((Date.now() / 1e3 - youngestTime) / (24 * 60 * 60))
|
|
1077
|
+
);
|
|
1078
|
+
return { sats_bonded, days_unspent: days_unspent2, confirmed_balance };
|
|
1079
|
+
}
|
|
1091
1080
|
const oldestTime = Math.min(
|
|
1092
1081
|
...confirmedUtxos.map((u) => u.status.block_time || Date.now() / 1e3)
|
|
1093
1082
|
);
|
|
1094
1083
|
const days_unspent = Math.max(0, Math.floor((Date.now() / 1e3 - oldestTime) / (24 * 60 * 60)));
|
|
1095
1084
|
return { sats_bonded, days_unspent, confirmed_balance };
|
|
1096
1085
|
}
|
|
1097
|
-
async function
|
|
1086
|
+
async function verify(input, opts = {}) {
|
|
1098
1087
|
const codes = [];
|
|
1099
1088
|
let network = "mainnet";
|
|
1100
1089
|
try {
|
|
@@ -1151,7 +1140,8 @@ async function verify2(input, opts = {}) {
|
|
|
1151
1140
|
});
|
|
1152
1141
|
const { sats_bonded, days_unspent, confirmed_balance } = computeMetrics(
|
|
1153
1142
|
utxos,
|
|
1154
|
-
parsed.bond
|
|
1143
|
+
parsed.bond,
|
|
1144
|
+
opts.coverSats
|
|
1155
1145
|
);
|
|
1156
1146
|
if (parsed.bond !== void 0 && parsed.bond > 0) {
|
|
1157
1147
|
if (confirmed_balance < parsed.bond) {
|
|
@@ -1249,14 +1239,14 @@ async function check(params) {
|
|
|
1249
1239
|
return { ok: false, sats: 0, days: 0, score: 0, reasons: ["not_found"] };
|
|
1250
1240
|
}
|
|
1251
1241
|
const shared = identity ? await backsOtherIdentity(env, identity, relays) : false;
|
|
1252
|
-
const outcome = await
|
|
1242
|
+
const outcome = await verify(
|
|
1253
1243
|
{
|
|
1254
1244
|
addr: env.address,
|
|
1255
1245
|
msg: env.message,
|
|
1256
1246
|
sig: env.signature,
|
|
1257
1247
|
scheme: env.scheme
|
|
1258
1248
|
},
|
|
1259
|
-
verifyOptions
|
|
1249
|
+
{ ...verifyOptions, coverSats: minSats > 0 ? minSats : void 0 }
|
|
1260
1250
|
);
|
|
1261
1251
|
const sats = outcome.metrics?.sats_bonded ?? 0;
|
|
1262
1252
|
const days = outcome.metrics?.days_unspent ?? 0;
|
|
@@ -1437,21 +1427,17 @@ async function verifyChallenge(opts) {
|
|
|
1437
1427
|
return { ok: false, reason: "purpose_mismatch" };
|
|
1438
1428
|
}
|
|
1439
1429
|
const scheme = opts.scheme ?? "bip322";
|
|
1440
|
-
let sigValid
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
if (!/^[1m][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(address)) {
|
|
1447
|
-
return { ok: false, reason: "sig_unsupported_scheme" };
|
|
1448
|
-
}
|
|
1449
|
-
sigValid = bitcoinMessage__namespace.verify(opts.message, address, opts.signature);
|
|
1450
|
-
} else {
|
|
1430
|
+
let sigValid;
|
|
1431
|
+
if (scheme === "bip322") {
|
|
1432
|
+
const sigBase64 = /^[0-9a-fA-F]+$/.test(opts.signature) && opts.signature.length % 2 === 0 ? buffer.Buffer.from(opts.signature, "hex").toString("base64") : opts.signature;
|
|
1433
|
+
sigValid = bip322.verifySignature(address, opts.message, sigBase64);
|
|
1434
|
+
} else if (scheme === "legacy") {
|
|
1435
|
+
if (!/^[1m][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(address)) {
|
|
1451
1436
|
return { ok: false, reason: "sig_unsupported_scheme" };
|
|
1452
1437
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1438
|
+
sigValid = bip322.verifySignature(address, opts.message, opts.signature);
|
|
1439
|
+
} else {
|
|
1440
|
+
return { ok: false, reason: "sig_unsupported_scheme" };
|
|
1455
1441
|
}
|
|
1456
1442
|
if (!sigValid) return { ok: false, reason: "sig_invalid" };
|
|
1457
1443
|
return {
|
|
@@ -1464,6 +1450,164 @@ async function verifyChallenge(opts) {
|
|
|
1464
1450
|
...purpose !== void 0 ? { purpose } : {}
|
|
1465
1451
|
};
|
|
1466
1452
|
}
|
|
1453
|
+
var HEADER = "orangecheck-bond";
|
|
1454
|
+
var ACK = "I pledge this coin to back this subject. It stays in my wallet.";
|
|
1455
|
+
var FIELDS = ["address", "outpoint", "subject", "nonce", "issued_at", "expires_at"];
|
|
1456
|
+
var BOND_SATS_FLOORS = [1e3, 1e4, 1e5, 1e6, 1e7];
|
|
1457
|
+
var BOND_DAYS_FLOORS = [7, 30, 90, 365];
|
|
1458
|
+
var BOND_MIN_CONFIRMATIONS = 6;
|
|
1459
|
+
var OUTPOINT_RE = /^[0-9a-f]{64}:(0|[1-9][0-9]{0,9})$/;
|
|
1460
|
+
var NONCE_RE = /^[0-9a-f]{32}$/;
|
|
1461
|
+
var TIME_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
|
|
1462
|
+
var SUBJECT_RE = /^[\x21-\x7e]{1,256}$/;
|
|
1463
|
+
function iso2(d) {
|
|
1464
|
+
return d.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
1465
|
+
}
|
|
1466
|
+
function randomNonce2() {
|
|
1467
|
+
const b = new Uint8Array(16);
|
|
1468
|
+
globalThis.crypto.getRandomValues(b);
|
|
1469
|
+
return utils_js.bytesToHex(b);
|
|
1470
|
+
}
|
|
1471
|
+
function wellFormed(f) {
|
|
1472
|
+
return bip322.decodeAddress(f.address) !== null && OUTPOINT_RE.test(f.outpoint) && SUBJECT_RE.test(f.subject) && NONCE_RE.test(f.nonce) && TIME_RE.test(f.issued_at) && TIME_RE.test(f.expires_at) && Date.parse(f.expires_at) > Date.parse(f.issued_at);
|
|
1473
|
+
}
|
|
1474
|
+
function buildBondMessage(fields) {
|
|
1475
|
+
if (!wellFormed(fields)) throw new Error("bond fields are malformed");
|
|
1476
|
+
return [HEADER, ...FIELDS.map((k) => `${k}: ${fields[k]}`), `ack: ${ACK}`].join("\n") + "\n";
|
|
1477
|
+
}
|
|
1478
|
+
function issueBondMessage(opts) {
|
|
1479
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
1480
|
+
const expires = new Date(now.getTime() + (opts.ttlSeconds ?? 600) * 1e3);
|
|
1481
|
+
const nonce = randomNonce2();
|
|
1482
|
+
const message = buildBondMessage({
|
|
1483
|
+
address: opts.address,
|
|
1484
|
+
outpoint: opts.outpoint,
|
|
1485
|
+
subject: opts.subject,
|
|
1486
|
+
nonce,
|
|
1487
|
+
issued_at: iso2(now),
|
|
1488
|
+
expires_at: iso2(expires)
|
|
1489
|
+
});
|
|
1490
|
+
return { message, nonce, expiresAt: expires.getTime() };
|
|
1491
|
+
}
|
|
1492
|
+
function parseBondMessage(message) {
|
|
1493
|
+
if (!message.endsWith("\n") || message.includes("\r")) return null;
|
|
1494
|
+
const lines = message.slice(0, -1).split("\n");
|
|
1495
|
+
if (lines.length !== FIELDS.length + 2 || lines[0] !== HEADER) return null;
|
|
1496
|
+
if (lines[lines.length - 1] !== `ack: ${ACK}`) return null;
|
|
1497
|
+
const out = {};
|
|
1498
|
+
for (const [i, key] of FIELDS.entries()) {
|
|
1499
|
+
const line = lines[i + 1];
|
|
1500
|
+
const prefix = `${key}: `;
|
|
1501
|
+
if (!line.startsWith(prefix)) return null;
|
|
1502
|
+
out[key] = line.slice(prefix.length);
|
|
1503
|
+
}
|
|
1504
|
+
const fields = out;
|
|
1505
|
+
return wellFormed(fields) && buildBondMessage(fields) === message ? fields : null;
|
|
1506
|
+
}
|
|
1507
|
+
function bondId(message) {
|
|
1508
|
+
return utils_js.bytesToHex(sha2_js.sha256(utils_js.utf8ToBytes(message)));
|
|
1509
|
+
}
|
|
1510
|
+
function bondNullifier(audience, outpoint) {
|
|
1511
|
+
return utils_js.bytesToHex(sha2_js.sha256(utils_js.utf8ToBytes(`orangecheck-bond-nullifier
|
|
1512
|
+
${audience}
|
|
1513
|
+
${outpoint}`)));
|
|
1514
|
+
}
|
|
1515
|
+
function bondFloors(sats, ageBlocks) {
|
|
1516
|
+
const days = Math.floor(ageBlocks / 144);
|
|
1517
|
+
const floor = (ladder, v) => ladder.filter((f) => v >= f).reduce((a, b) => Math.max(a, b), 0);
|
|
1518
|
+
return { sats_floor: floor(BOND_SATS_FLOORS, sats), days_floor: floor(BOND_DAYS_FLOORS, days) };
|
|
1519
|
+
}
|
|
1520
|
+
function verifyBondSignature(opts) {
|
|
1521
|
+
const fields = parseBondMessage(opts.message);
|
|
1522
|
+
if (!fields) return { ok: false, reason: "bond_malformed" };
|
|
1523
|
+
if (opts.subject !== void 0 && fields.subject !== opts.subject) {
|
|
1524
|
+
return { ok: false, reason: "bond_subject_mismatch" };
|
|
1525
|
+
}
|
|
1526
|
+
const now = (opts.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
1527
|
+
const skew = (opts.skewSeconds ?? 60) * 1e3;
|
|
1528
|
+
if (Date.parse(fields.issued_at) - skew > now) return { ok: false, reason: "bond_not_yet_valid" };
|
|
1529
|
+
if (Date.parse(fields.expires_at) + skew < now) return { ok: false, reason: "bond_expired" };
|
|
1530
|
+
if (!bip322.verifySignature(fields.address, opts.message, opts.signature)) {
|
|
1531
|
+
return { ok: false, reason: "sig_invalid" };
|
|
1532
|
+
}
|
|
1533
|
+
return { ok: true, fields, bond_id: bondId(opts.message) };
|
|
1534
|
+
}
|
|
1535
|
+
async function checkBondCoin(opts) {
|
|
1536
|
+
const decoded = bip322.decodeAddress(opts.address);
|
|
1537
|
+
if (!decoded || !OUTPOINT_RE.test(opts.outpoint)) return { ok: false, reason: "bond_malformed" };
|
|
1538
|
+
let coin;
|
|
1539
|
+
let tip;
|
|
1540
|
+
try {
|
|
1541
|
+
[coin, tip] = await Promise.all([opts.source.coin(opts.outpoint), opts.source.tip()]);
|
|
1542
|
+
} catch {
|
|
1543
|
+
return { ok: false, reason: "chain_unavailable" };
|
|
1544
|
+
}
|
|
1545
|
+
if (!coin) return { ok: false, reason: "coin_not_found" };
|
|
1546
|
+
if (coin.scriptPubKey !== utils_js.bytesToHex(decoded.scriptPubKey)) {
|
|
1547
|
+
return { ok: false, reason: "coin_address_mismatch" };
|
|
1548
|
+
}
|
|
1549
|
+
if (coin.confirmedHeight === null) return { ok: false, reason: "coin_unconfirmed" };
|
|
1550
|
+
if (coin.spent) return { ok: false, reason: "coin_spent" };
|
|
1551
|
+
const confirmations = tip.height - coin.confirmedHeight + 1;
|
|
1552
|
+
if (confirmations < (opts.minConfirmations ?? BOND_MIN_CONFIRMATIONS)) {
|
|
1553
|
+
return { ok: false, reason: "coin_too_recent" };
|
|
1554
|
+
}
|
|
1555
|
+
return { ok: true, sats: coin.sats, confirmed_height: coin.confirmedHeight, tip };
|
|
1556
|
+
}
|
|
1557
|
+
async function verifyBond(opts) {
|
|
1558
|
+
const sig = verifyBondSignature(opts);
|
|
1559
|
+
if (!sig.ok) return sig;
|
|
1560
|
+
const coin = await checkBondCoin({
|
|
1561
|
+
address: sig.fields.address,
|
|
1562
|
+
outpoint: sig.fields.outpoint,
|
|
1563
|
+
source: opts.source,
|
|
1564
|
+
minConfirmations: opts.minConfirmations
|
|
1565
|
+
});
|
|
1566
|
+
if (!coin.ok) return coin;
|
|
1567
|
+
return {
|
|
1568
|
+
ok: true,
|
|
1569
|
+
bond: {
|
|
1570
|
+
bond_id: sig.bond_id,
|
|
1571
|
+
subject: sig.fields.subject,
|
|
1572
|
+
address: sig.fields.address,
|
|
1573
|
+
outpoint: sig.fields.outpoint,
|
|
1574
|
+
sats: coin.sats,
|
|
1575
|
+
confirmed_height: coin.confirmed_height,
|
|
1576
|
+
tip: coin.tip
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
function esploraSource(base = "https://mempool.space/api", opts = {}) {
|
|
1581
|
+
const get = async (path) => {
|
|
1582
|
+
const r = await (opts.fetch ?? fetch)(`${base}${path}`, {
|
|
1583
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 5e3)
|
|
1584
|
+
});
|
|
1585
|
+
if (!r.ok && r.status !== 404) throw new Error(`esplora ${path}: HTTP ${r.status}`);
|
|
1586
|
+
return r;
|
|
1587
|
+
};
|
|
1588
|
+
return {
|
|
1589
|
+
async tip() {
|
|
1590
|
+
const [h, hash] = await Promise.all([get("/blocks/tip/height"), get("/blocks/tip/hash")]);
|
|
1591
|
+
return { height: Number(await h.text()), hash: (await hash.text()).trim() };
|
|
1592
|
+
},
|
|
1593
|
+
async coin(outpoint) {
|
|
1594
|
+
const [txid, voutStr] = outpoint.split(":");
|
|
1595
|
+
const vout = Number(voutStr);
|
|
1596
|
+
const txr = await get(`/tx/${txid}`);
|
|
1597
|
+
if (txr.status === 404) return null;
|
|
1598
|
+
const tx = await txr.json();
|
|
1599
|
+
const out = tx.vout[vout];
|
|
1600
|
+
if (!out) return null;
|
|
1601
|
+
const spend = await (await get(`/tx/${txid}/outspend/${vout}`)).json();
|
|
1602
|
+
return {
|
|
1603
|
+
sats: out.value,
|
|
1604
|
+
scriptPubKey: out.scriptpubkey.toLowerCase(),
|
|
1605
|
+
confirmedHeight: tx.status.confirmed ? tx.status.block_height ?? null : null,
|
|
1606
|
+
spent: spend.spent
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1467
1611
|
|
|
1468
1612
|
// src/utils/rate-limiter.ts
|
|
1469
1613
|
var RateLimitError = class extends Error {
|
|
@@ -2253,17 +2397,26 @@ var STATUS_META = {
|
|
|
2253
2397
|
}
|
|
2254
2398
|
};
|
|
2255
2399
|
|
|
2400
|
+
exports.BOND_DAYS_FLOORS = BOND_DAYS_FLOORS;
|
|
2401
|
+
exports.BOND_MIN_CONFIRMATIONS = BOND_MIN_CONFIRMATIONS;
|
|
2402
|
+
exports.BOND_SATS_FLOORS = BOND_SATS_FLOORS;
|
|
2256
2403
|
exports.DEFAULT_RELAYS = DEFAULT_RELAYS;
|
|
2257
2404
|
exports.FANOUT_DEADLINE_MS = FANOUT_DEADLINE_MS;
|
|
2258
2405
|
exports.STATUS_META = STATUS_META;
|
|
2406
|
+
exports.bondFloors = bondFloors;
|
|
2407
|
+
exports.bondId = bondId;
|
|
2408
|
+
exports.bondNullifier = bondNullifier;
|
|
2409
|
+
exports.buildBondMessage = buildBondMessage;
|
|
2259
2410
|
exports.buildCanonicalMessage = buildCanonicalMessage;
|
|
2260
2411
|
exports.check = check;
|
|
2412
|
+
exports.checkBondCoin = checkBondCoin;
|
|
2261
2413
|
exports.computeAllScores = computeAllScores;
|
|
2262
2414
|
exports.computeScore = computeScore;
|
|
2263
2415
|
exports.createAttestation = createAttestation;
|
|
2264
2416
|
exports.createAttestationEnvelope = createAttestationEnvelope;
|
|
2265
2417
|
exports.createAttestationEvent = createAttestationEvent;
|
|
2266
2418
|
exports.discoverAttestations = discoverAttestations;
|
|
2419
|
+
exports.esploraSource = esploraSource;
|
|
2267
2420
|
exports.extractAttestationIdFromUrl = extractAttestationIdFromUrl;
|
|
2268
2421
|
exports.formatIdentities = formatIdentities;
|
|
2269
2422
|
exports.formatIdentitiesForDisplay = formatIdentitiesForDisplay;
|
|
@@ -2293,10 +2446,12 @@ exports.isNip07Available = isNip07Available;
|
|
|
2293
2446
|
exports.isNostrIdentityVerified = isNostrIdentityVerified;
|
|
2294
2447
|
exports.isValidDomain = isValidDomain;
|
|
2295
2448
|
exports.isValidTweetUrl = isValidTweetUrl;
|
|
2449
|
+
exports.issueBondMessage = issueBondMessage;
|
|
2296
2450
|
exports.issueChallenge = issueChallenge;
|
|
2297
2451
|
exports.nostrIdentifierForms = nostrIdentifierForms;
|
|
2298
2452
|
exports.nostrPubkeyToHex = nostrPubkeyToHex;
|
|
2299
2453
|
exports.parseAttestationFromEvent = parseAttestationFromEvent;
|
|
2454
|
+
exports.parseBondMessage = parseBondMessage;
|
|
2300
2455
|
exports.parseIdentities = parseIdentities;
|
|
2301
2456
|
exports.publishAttestation = publishAttestation;
|
|
2302
2457
|
exports.publishToRelays = publishToRelays;
|
|
@@ -2307,9 +2462,11 @@ exports.queryRecent = queryRecent;
|
|
|
2307
2462
|
exports.requiresManualVerification = requiresManualVerification;
|
|
2308
2463
|
exports.signNostrEvent = signNostrEvent;
|
|
2309
2464
|
exports.supportsAutomaticVerification = supportsAutomaticVerification;
|
|
2310
|
-
exports.verify =
|
|
2465
|
+
exports.verify = verify;
|
|
2311
2466
|
exports.verifyAllIdentities = verifyAllIdentities;
|
|
2312
2467
|
exports.verifyAttestationById = verifyAttestationById;
|
|
2468
|
+
exports.verifyBond = verifyBond;
|
|
2469
|
+
exports.verifyBondSignature = verifyBondSignature;
|
|
2313
2470
|
exports.verifyChallenge = verifyChallenge;
|
|
2314
2471
|
exports.verifyDnsIdentity = verifyDnsIdentity;
|
|
2315
2472
|
exports.verifyGitHubIdentity = verifyGitHubIdentity;
|