Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: rename some stuff; code format #11

Merged
merged 1 commit into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "silent-payments",
"version": "0.3.1",
"version": "0.3.2",
"description": "BIP-352",
"main": "src/index.ts",
"scripts": {
Expand Down
59 changes: 28 additions & 31 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,30 @@ import ecc from "./noble_ecc";
const ECPair = ECPairFactory(ecc);
bitcoin.initEccLib(ecc);

export type UTXOType = 'p2wpkh' | 'p2sh-p2wpkh' | 'p2pkh' | 'p2tr' | 'non-eligible';
type UTXO = {
export type UTXOType = "p2wpkh" | "p2sh-p2wpkh" | "p2pkh" | "p2tr" | "non-eligible";

export type UTXO = {
txid: string;
vout: number;
WIF: string;
wif: string;
utxoType: UTXOType;
};

type Target = {
silentPaymentCode?: string;
address?: string;
export type Target = {
address: string; // either address or payment code
value?: number;
};

type SilentPaymentGroup = {
export type SilentPaymentGroup = {
Bscan: Buffer;
BmValues: Array<[Buffer, number | undefined]>;
};

function taggedHash(tag: string, data: Buffer): Buffer {
const hash = crypto.createHash('sha256');
const tagHash = hash.update(tag, 'utf-8').digest();
const ss = Buffer.concat([tagHash, tagHash, data]);
return crypto.createHash('sha256').update(ss).digest();
const hash = crypto.createHash("sha256");
const tagHash = hash.update(tag, "utf-8").digest();
const ss = Buffer.concat([tagHash, tagHash, data]);
return crypto.createHash("sha256").update(ss).digest();
}

const G = Buffer.from("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "hex");
Expand All @@ -49,12 +49,12 @@ export class SilentPayment {

const silentPaymentGroups: Array<SilentPaymentGroup> = [];
for (const target of targets) {
if (!target.silentPaymentCode) {
if (!target.address.startsWith("sp1")) {
ret.push(target); // passthrough
continue;
}

const result = bech32m.decode(target.silentPaymentCode, 118);
const result = bech32m.decode(target.address, 118);
const version = result.words.shift();
if (version !== 0) {
throw new Error("Unexpected version of silent payment code");
Expand Down Expand Up @@ -88,10 +88,7 @@ export class SilentPayment {

let k = 0;
for (const [Bm, amount] of group.BmValues) {
const tk = taggedHash(
"BIP0352/SharedSecret",
Buffer.concat([ecdh_shared_secret!, SilentPayment._ser32(k)])
);
const tk = taggedHash("BIP0352/SharedSecret", Buffer.concat([ecdh_shared_secret!, SilentPayment._ser32(k)]));

// Let Pmk = tk·G + Bm
const Pmk = Buffer.from(ecc.pointAdd(ecc.pointMultiply(G, tk) as Uint8Array, Bm) as Uint8Array);
Expand Down Expand Up @@ -141,18 +138,18 @@ export class SilentPayment {
throw new Error("No UTXOs provided");
}

const keys: Array<Buffer> = []
const keys: Array<Buffer> = [];
for (const utxo of utxos) {
let key = ECPair.fromWIF(utxo.WIF).privateKey;
let key = ECPair.fromWIF(utxo.wif).privateKey;
switch (utxo.utxoType) {
case 'non-eligible':
// Non-eligible UTXOs can be spent in the transaction, but are not used for the
// shared secret derivation. Note: we don't check that the private key is valid
// for non-eligible utxos because its possible the sender is following a different
// signing protocol for these utxos. For silent payments eligible utxos, we require
// access to the private key.
break;
case 'p2tr':
case "non-eligible":
// Non-eligible UTXOs can be spent in the transaction, but are not used for the
// shared secret derivation. Note: we don't check that the private key is valid
// for non-eligible utxos because its possible the sender is following a different
// signing protocol for these utxos. For silent payments eligible utxos, we require
// access to the private key.
break;
case "p2tr":
if (key === undefined) {
throw new Error("No private key found for eligible UTXO");
}
Expand All @@ -161,9 +158,9 @@ export class SilentPayment {
if (ecc.pointFromScalar(key)![0] === 0x03) {
key = Buffer.from(ecc.privateNegate(key));
}
case 'p2wpkh':
case 'p2sh-p2wpkh':
case 'p2pkh':
case "p2wpkh":
case "p2sh-p2wpkh":
case "p2pkh":
if (key === undefined) {
throw new Error("No private key found for eligible UTXO");
}
Expand All @@ -181,7 +178,7 @@ export class SilentPayment {
return Buffer.from(ecc.privateAdd(acc, key) as Uint8Array);
});

return ret
return ret;
}

static isPaymentCodeValid(pc: string) {
Expand Down
122 changes: 63 additions & 59 deletions tests/silent-payment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import jsonInput from "./data/sending_test_vectors.json";
const ECPair = ECPairFactory(ecc);

function exactMatch(a: string[], b: string[]): boolean {
const sortedA = a.sort();
const sortedB = b.sort();
return sortedA.length === sortedB.length && sortedA.every((value, index) => value === sortedB[index]);
const sortedA = a.sort();
const sortedB = b.sort();
return sortedA.length === sortedB.length && sortedA.every((value, index) => value === sortedB[index]);
}

function matchSubset(generated: string[], expected: string[][]): boolean {
return expected.some(subArray => exactMatch(generated, subArray));
return expected.some((subArray) => exactMatch(generated, subArray));
}

type Given = {
Expand All @@ -32,8 +32,8 @@ type Sending = {
};

type TestCase = {
comment: string;
sending: Sending[];
comment: string;
sending: Sending[];
};

const tests = jsonInput as unknown as Array<TestCase>;
Expand All @@ -46,34 +46,34 @@ it("smoke test", () => {
/* Sending tests from the BIP352 test vectors */
tests.forEach((testCase, index) => {
// Prepare the 'inputs' array
testCase.sending.forEach(sending => {
const utxos = sending.given.vin.map((input) => ({
txid: input.txid,
vout: input.vout,
WIF: ECPair.fromPrivateKey(Buffer.from(input.private_key, "hex")).toWIF(),
utxoType: getUTXOType(input) as UTXOType,
}));
const noEligibleUtxos = utxos.every(utxo => utxo.utxoType === 'non-eligible');

// Prepare the 'recipients' array
const recipients = sending.given.recipients.map((recipient) => ({
silentPaymentCode: recipient,
value: 1,
}));

it(`Test Case: ${testCase.comment}`, () => {
const sp = new SilentPayment();
if (noEligibleUtxos) {
expect(() => {
sp.createTransaction(utxos, recipients);
}).toThrow("No eligible UTXOs with private keys found");
} else {
const generated = sp.createTransaction(utxos, recipients);
const generated_pubkeys: string[] = generated.map(obj => obj.address).filter(Boolean) as string[];
assert(matchSubset(generated_pubkeys, sending.expected.outputs));
}
});
testCase.sending.forEach((sending) => {
const utxos = sending.given.vin.map((input) => ({
txid: input.txid,
vout: input.vout,
wif: ECPair.fromPrivateKey(Buffer.from(input.private_key, "hex")).toWIF(),
utxoType: getUTXOType(input) as UTXOType,
}));
const noEligibleUtxos = utxos.every((utxo) => utxo.utxoType === "non-eligible");

// Prepare the 'recipients' array
const recipients = sending.given.recipients.map((recipient) => ({
address: recipient,
value: 1,
}));

it(`Test Case: ${testCase.comment}`, () => {
const sp = new SilentPayment();
if (noEligibleUtxos) {
expect(() => {
sp.createTransaction(utxos, recipients);
}).toThrow("No eligible UTXOs with private keys found");
} else {
const generated = sp.createTransaction(utxos, recipients);
const generated_pubkeys: string[] = generated.map((obj) => obj.address).filter(Boolean) as string[];
assert(matchSubset(generated_pubkeys, sending.expected.outputs));
}
});
});
});

it("2 inputs - 0 SP outputs (just a passthrough)", () => {
Expand All @@ -84,13 +84,13 @@ it("2 inputs - 0 SP outputs (just a passthrough)", () => {
{
txid: "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16",
vout: 0,
WIF: ECPair.fromPrivateKey(Buffer.from("1cd5e8f6b3f29505ed1da7a5806291ebab6491c6a172467e44debe255428a192", "hex")).toWIF(),
wif: ECPair.fromPrivateKey(Buffer.from("1cd5e8f6b3f29505ed1da7a5806291ebab6491c6a172467e44debe255428a192", "hex")).toWIF(),
utxoType: "p2wpkh",
},
{
txid: "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d",
vout: 0,
WIF: ECPair.fromPrivateKey(Buffer.from("7416ef4d92e4dd09d680af6999d1723816e781c030f4b4ecb5bf46939ca30056", "hex")).toWIF(),
wif: ECPair.fromPrivateKey(Buffer.from("7416ef4d92e4dd09d680af6999d1723816e781c030f4b4ecb5bf46939ca30056", "hex")).toWIF(),
utxoType: "p2wpkh",
},
],
Expand Down Expand Up @@ -121,33 +121,37 @@ it("2 inputs - 0 SP outputs (just a passthrough)", () => {
it("SilentPayment._outpointHash() works", () => {
const A = ECPair.fromWIF("L4cJGJp4haLbS46ZKMKrjt7HqVuYTSHkChykdMrni955Fs3Sb8vq").publicKey;
assert.deepStrictEqual(
SilentPayment._outpointsHash([
{
txid: "a2365547d16b555593e3f58a2b67143fc8ab84e7e1257b1c13d2a9a2ec3a2efb",
vout: 0,
WIF: "",
utxoType: "p2wpkh",
},
],
A).toString("hex"),
SilentPayment._outpointsHash(
[
{
txid: "a2365547d16b555593e3f58a2b67143fc8ab84e7e1257b1c13d2a9a2ec3a2efb",
vout: 0,
wif: "",
utxoType: "p2wpkh",
},
],
A
).toString("hex"),
"94d5923201f2f239e4d2d5a44239e0377325a343e4c068cfd078217adc663d7c"
);
assert.deepStrictEqual(
SilentPayment._outpointsHash([
{
txid: "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16",
vout: 0,
WIF: "",
utxoType: "non-eligible"
},
{
txid: "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d",
vout: 0,
WIF: "",
utxoType: "p2wpkh",
},
],
A).toString("hex"),
SilentPayment._outpointsHash(
[
{
txid: "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16",
vout: 0,
wif: "",
utxoType: "non-eligible",
},
{
txid: "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d",
vout: 0,
wif: "",
utxoType: "p2wpkh",
},
],
A
).toString("hex"),
"3ea0693eeb0c7e848ad7b875f1998e9ed02905e88a6f5c45f25fa187b7f073d2"
);
});
Expand Down
Loading