Skip to content

Commit

Permalink
refactor: rename some stuff; code format
Browse files Browse the repository at this point in the history
  • Loading branch information
Overtorment committed May 29, 2024
1 parent a221b90 commit ce01f2d
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 91 deletions.
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

0 comments on commit ce01f2d

Please sign in to comment.