@ledgerhq/psbtv2 0.1.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.
Files changed (46) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/LICENSE.txt +21 -0
  3. package/README.md +91 -0
  4. package/jest.config.js +26 -0
  5. package/lib/buffertools.d.ts +29 -0
  6. package/lib/buffertools.d.ts.map +1 -0
  7. package/lib/buffertools.js +129 -0
  8. package/lib/buffertools.js.map +1 -0
  9. package/lib/index.d.ts +4 -0
  10. package/lib/index.d.ts.map +1 -0
  11. package/lib/index.js +18 -0
  12. package/lib/index.js.map +1 -0
  13. package/lib/psbtParsing.d.ts +15 -0
  14. package/lib/psbtParsing.d.ts.map +1 -0
  15. package/lib/psbtParsing.js +52 -0
  16. package/lib/psbtParsing.js.map +1 -0
  17. package/lib/psbtv2.d.ts +200 -0
  18. package/lib/psbtv2.d.ts.map +1 -0
  19. package/lib/psbtv2.js +647 -0
  20. package/lib/psbtv2.js.map +1 -0
  21. package/lib-es/buffertools.d.ts +29 -0
  22. package/lib-es/buffertools.d.ts.map +1 -0
  23. package/lib-es/buffertools.js +119 -0
  24. package/lib-es/buffertools.js.map +1 -0
  25. package/lib-es/index.d.ts +4 -0
  26. package/lib-es/index.d.ts.map +1 -0
  27. package/lib-es/index.js +4 -0
  28. package/lib-es/index.js.map +1 -0
  29. package/lib-es/psbtParsing.d.ts +15 -0
  30. package/lib-es/psbtParsing.d.ts.map +1 -0
  31. package/lib-es/psbtParsing.js +48 -0
  32. package/lib-es/psbtParsing.js.map +1 -0
  33. package/lib-es/psbtv2.d.ts +200 -0
  34. package/lib-es/psbtv2.d.ts.map +1 -0
  35. package/lib-es/psbtv2.js +641 -0
  36. package/lib-es/psbtv2.js.map +1 -0
  37. package/package.json +78 -0
  38. package/src/buffertools.test.ts +116 -0
  39. package/src/buffertools.ts +137 -0
  40. package/src/fromV0.test.ts +577 -0
  41. package/src/index.ts +3 -0
  42. package/src/psbtParsing.test.ts +86 -0
  43. package/src/psbtParsing.ts +51 -0
  44. package/src/psbtv2.test.ts +441 -0
  45. package/src/psbtv2.ts +740 -0
  46. package/tsconfig.json +9 -0
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@ledgerhq/psbtv2",
3
+ "version": "0.1.0",
4
+ "description": "Partially Signed Bitcoin Transaction version 2 (BIP370) implementation",
5
+ "keywords": [
6
+ "Ledger",
7
+ "Bitcoin",
8
+ "PSBT",
9
+ "BIP370",
10
+ "BIP174"
11
+ ],
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/LedgerHQ/ledger-live.git"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/LedgerHQ/ledger-live/issues"
18
+ },
19
+ "homepage": "https://github.com/LedgerHQ/ledger-live/tree/develop/libs/psbtv2",
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "main": "lib/index.js",
24
+ "module": "lib-es/index.js",
25
+ "types": "lib/index.d.ts",
26
+ "typesVersions": {
27
+ "*": {
28
+ "lib/*": [
29
+ "lib/*"
30
+ ],
31
+ "lib-es/*": [
32
+ "lib-es/*"
33
+ ],
34
+ "*": [
35
+ "lib/*"
36
+ ]
37
+ }
38
+ },
39
+ "exports": {
40
+ "./lib/*": "./lib/*.js",
41
+ "./lib-es/*": "./lib-es/*.js",
42
+ "./*": {
43
+ "require": "./lib/*.js",
44
+ "default": "./lib-es/*.js"
45
+ },
46
+ ".": {
47
+ "require": "./lib/index.js",
48
+ "default": "./lib-es/index.js"
49
+ },
50
+ "./package.json": "./package.json"
51
+ },
52
+ "license": "Apache-2.0",
53
+ "dependencies": {
54
+ "bitcoinjs-lib": "^5.2.0",
55
+ "varuint-bitcoin": "1.1.2"
56
+ },
57
+ "devDependencies": {
58
+ "@types/jest": "30.0.0",
59
+ "@types/node": "^22.10.10",
60
+ "@swc/core": "1.15.8",
61
+ "@swc/jest": "0.2.39",
62
+ "jest": "30.2.0",
63
+ "rimraf": "^4.4.1",
64
+ "typescript": "^5.4.3"
65
+ },
66
+ "scripts": {
67
+ "clean": "rimraf lib lib-es",
68
+ "build": "tsc && tsc -m esnext --moduleResolution bundler --outDir lib-es",
69
+ "prewatch": "pnpm build",
70
+ "watch": "tsc --watch",
71
+ "watch:es": "tsc --watch -m esnext --moduleResolution bundler --outDir lib-es",
72
+ "lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx --cache",
73
+ "lint:fix": "pnpm lint --fix",
74
+ "test": "jest",
75
+ "coverage": "jest --coverage",
76
+ "typecheck": "tsc --noEmit"
77
+ }
78
+ }
@@ -0,0 +1,116 @@
1
+ import { BufferReader, BufferWriter, unsafeFrom64bitLE, unsafeTo64bitLE } from "./buffertools";
2
+
3
+ describe("buffertools", () => {
4
+ describe("unsafeTo64bitLE", () => {
5
+ it("should roundtrip values within safe range", () => {
6
+ const value = Number.MAX_SAFE_INTEGER - 1;
7
+ const buf = unsafeTo64bitLE(value);
8
+ expect(buf).toHaveLength(8);
9
+ const back = unsafeFrom64bitLE(buf);
10
+ expect(back).toBe(value);
11
+ });
12
+
13
+ it("should throw for numbers greater than MAX_SAFE_INT", () => {
14
+ const above = Number.MAX_SAFE_INTEGER + 1;
15
+ expect(() => unsafeTo64bitLE(above)).toThrow("Can't convert numbers > MAX_SAFE_INT");
16
+ });
17
+ });
18
+
19
+ describe("unsafeFrom64bitLE", () => {
20
+ it("should throw if buffer length is not 8", () => {
21
+ expect(() => unsafeFrom64bitLE(Buffer.alloc(4))).toThrow("Expected Buffer of length 8");
22
+ });
23
+
24
+ it("should throw if highest byte is non-zero", () => {
25
+ const buf = Buffer.alloc(8, 0);
26
+ buf[7] = 1;
27
+ expect(() => unsafeFrom64bitLE(buf)).toThrow("Can't encode numbers > MAX_SAFE_INT");
28
+ });
29
+
30
+ it("should throw if second-highest byte exceeds 0x1f", () => {
31
+ const buf = Buffer.alloc(8, 0);
32
+ buf[6] = 0x20;
33
+ expect(() => unsafeFrom64bitLE(buf)).toThrow("Can't encode numbers > MAX_SAFE_INT");
34
+ });
35
+ });
36
+
37
+ describe("BufferReader", () => {
38
+ it("should throw when readSlice goes out of bounds", () => {
39
+ const reader = new BufferReader(Buffer.alloc(2));
40
+ reader.readSlice(2);
41
+ expect(() => reader.readSlice(1)).toThrow("Cannot read slice out of bounds");
42
+ });
43
+
44
+ it("should read and write vectors correctly with BufferWriter", () => {
45
+ const writer = new BufferWriter();
46
+ const elements = [Buffer.from("aa", "hex"), Buffer.from("bbbb", "hex")];
47
+
48
+ writer.writeVarInt(elements.length);
49
+ for (const el of elements) {
50
+ writer.writeVarSlice(el);
51
+ }
52
+
53
+ const buf = writer.buffer();
54
+ const reader = new BufferReader(buf);
55
+ const vector = reader.readVector();
56
+
57
+ expect(vector).toHaveLength(elements.length);
58
+ expect(vector[0]).toEqual(elements[0]);
59
+ expect(vector[1]).toEqual(elements[1]);
60
+ });
61
+
62
+ it("should support basic integer and slice read/write helpers", () => {
63
+ const writer = new BufferWriter();
64
+
65
+ writer.writeUInt8(0x12);
66
+ writer.writeInt32(-42);
67
+ writer.writeUInt32(0xdeadbeef);
68
+ writer.writeUInt64(123456789);
69
+ writer.writeVarInt(3);
70
+ writer.writeSlice(Buffer.from("abcd", "hex"));
71
+ writer.writeVarSlice(Buffer.from("ef", "hex"));
72
+
73
+ const buf = writer.buffer();
74
+ const reader = new BufferReader(buf);
75
+
76
+ expect(reader.readUInt8()).toBe(0x12);
77
+ expect(reader.readInt32()).toBe(-42);
78
+ expect(reader.readUInt32()).toBe(0xdeadbeef);
79
+ expect(reader.readUInt64()).toBe(123456789);
80
+
81
+ const vi = reader.readVarInt();
82
+ expect(vi).toBe(3);
83
+
84
+ const slice = reader.readSlice(2);
85
+ expect(slice.toString("hex")).toBe("abcd");
86
+
87
+ const varSlice = reader.readVarSlice();
88
+ expect(varSlice.toString("hex")).toBe("ef");
89
+ });
90
+ });
91
+
92
+ // Copied from the old test suite
93
+ function run(n: number, expectedHex: string) {
94
+ const w = new BufferWriter();
95
+ w.writeUInt64(n);
96
+ expect(w.buffer()).toEqual(Buffer.from(expectedHex, "hex"));
97
+ const r = new BufferReader(w.buffer());
98
+ expect(r.readUInt64()).toEqual(n);
99
+ }
100
+
101
+ test("Test 64 bit numbers", () => {
102
+ run(0, "0000000000000000");
103
+ run(1, "0100000000000000");
104
+ run(0xffffffff, "ffffffff00000000");
105
+ run(0x0100000000, "0000000001000000");
106
+ run(0x010203040506, "0605040302010000");
107
+ run(Number.MAX_SAFE_INTEGER, "FFFFFFFFFFFF1F00");
108
+ });
109
+
110
+ test("Too big 64 bit number", () => {
111
+ const w = new BufferWriter();
112
+ expect(() => w.writeUInt64(Number.MAX_SAFE_INTEGER + 1)).toThrow();
113
+ const r = new BufferReader(Buffer.from("FFFFFFFFFFFF2000", "hex"));
114
+ expect(() => r.readUInt64()).toThrow();
115
+ });
116
+ });
@@ -0,0 +1,137 @@
1
+ import varuint from "varuint-bitcoin";
2
+
3
+ export function unsafeTo64bitLE(n: number): Buffer {
4
+ // we want to represent the input as a 8-bytes array
5
+ if (n > Number.MAX_SAFE_INTEGER) {
6
+ throw new Error("Can't convert numbers > MAX_SAFE_INT");
7
+ }
8
+ const byteArray = Buffer.alloc(8, 0);
9
+ for (let index = 0; index < byteArray.length; index++) {
10
+ const byte = n & 0xff;
11
+ byteArray[index] = byte;
12
+ n = (n - byte) / 256;
13
+ }
14
+ return byteArray;
15
+ }
16
+
17
+ export function unsafeFrom64bitLE(byteArray: Buffer): number {
18
+ let value = 0;
19
+ if (byteArray.length != 8) {
20
+ throw new Error("Expected Buffer of length 8");
21
+ }
22
+ if (byteArray[7] != 0) {
23
+ throw new Error("Can't encode numbers > MAX_SAFE_INT");
24
+ }
25
+ if (byteArray[6] > 0x1f) {
26
+ throw new Error("Can't encode numbers > MAX_SAFE_INT");
27
+ }
28
+ for (let i = byteArray.length - 1; i >= 0; i--) {
29
+ value = value * 256 + byteArray[i];
30
+ }
31
+ return value;
32
+ }
33
+
34
+ export class BufferWriter {
35
+ private readonly bufs: Buffer[] = [];
36
+
37
+ write(alloc: number, fn: (b: Buffer) => void): void {
38
+ const b = Buffer.alloc(alloc);
39
+ fn(b);
40
+ this.bufs.push(b);
41
+ }
42
+
43
+ writeUInt8(i: number): void {
44
+ this.write(1, b => b.writeUInt8(i, 0));
45
+ }
46
+
47
+ writeInt32(i: number): void {
48
+ this.write(4, b => b.writeInt32LE(i, 0));
49
+ }
50
+
51
+ writeUInt32(i: number): void {
52
+ this.write(4, b => b.writeUInt32LE(i, 0));
53
+ }
54
+
55
+ writeUInt64(i: number): void {
56
+ const bytes = unsafeTo64bitLE(i);
57
+ this.writeSlice(bytes);
58
+ }
59
+
60
+ writeVarInt(i: number): void {
61
+ this.bufs.push(varuint.encode(i));
62
+ }
63
+
64
+ writeSlice(slice: Buffer): void {
65
+ this.bufs.push(Buffer.from(slice));
66
+ }
67
+
68
+ writeVarSlice(slice: Buffer): void {
69
+ this.writeVarInt(slice.length);
70
+ this.writeSlice(slice);
71
+ }
72
+
73
+ buffer(): Buffer {
74
+ return Buffer.concat(this.bufs);
75
+ }
76
+ }
77
+
78
+ export class BufferReader {
79
+ constructor(
80
+ public buffer: Buffer,
81
+ public offset: number = 0,
82
+ ) {}
83
+
84
+ available(): number {
85
+ return this.buffer.length - this.offset;
86
+ }
87
+
88
+ readUInt8(): number {
89
+ const result = this.buffer.readUInt8(this.offset);
90
+ this.offset++;
91
+ return result;
92
+ }
93
+
94
+ readInt32(): number {
95
+ const result = this.buffer.readInt32LE(this.offset);
96
+ this.offset += 4;
97
+ return result;
98
+ }
99
+
100
+ readUInt32(): number {
101
+ const result = this.buffer.readUInt32LE(this.offset);
102
+ this.offset += 4;
103
+ return result;
104
+ }
105
+
106
+ readUInt64(): number {
107
+ const buf = this.readSlice(8);
108
+ const n = unsafeFrom64bitLE(buf);
109
+ return n;
110
+ }
111
+
112
+ readVarInt(): number {
113
+ const vi = varuint.decode(this.buffer, this.offset);
114
+ this.offset += varuint.decode.bytes;
115
+ return vi;
116
+ }
117
+
118
+ readSlice(n: number): Buffer {
119
+ if (this.buffer.length < this.offset + n) {
120
+ throw new Error("Cannot read slice out of bounds");
121
+ }
122
+ const result = this.buffer.slice(this.offset, this.offset + n);
123
+ this.offset += n;
124
+ return result;
125
+ }
126
+
127
+ readVarSlice(): Buffer {
128
+ return this.readSlice(this.readVarInt());
129
+ }
130
+
131
+ readVector(): Buffer[] {
132
+ const count = this.readVarInt();
133
+ const vector: Buffer[] = [];
134
+ for (let i = 0; i < count; i++) vector.push(this.readVarSlice());
135
+ return vector;
136
+ }
137
+ }