gitmark 0.0.69 → 0.0.71

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 CHANGED
@@ -1,159 +1,99 @@
1
- <div align="center">
2
- <h1>gitmark</h1>
3
- </div>
1
+ # git mark
4
2
 
5
- <div align="center">
6
- Mark your git commits, to create global consensus, and a definitive project history
7
- </div>
3
+ Anchor git commits to Bitcoin via [blocktrails](https://blocktrails.org) key chaining.
8
4
 
9
- ---
5
+ [![npm](https://img.shields.io/npm/v/gitmark)](https://npmjs.com/package/gitmark)
6
+ [![license](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](./LICENSE)
10
7
 
11
- <div align="center">
12
- <h4>Getting Started</h4>
13
- </div>
14
-
15
- ---
16
-
17
- [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/solidpayorg/git-mark/blob/gh-pages/LICENSE)
18
- ![npm](https://img.shields.io/npm/v/gitmark)
19
- [![npm](https://img.shields.io/npm/dw/gitmark.svg)](https://npmjs.com/package/gitmark)
20
- [![Github Stars](https://img.shields.io/github/stars/solidpayorg/gitmark.svg)](https://github.com/solidpayorg/gitmark/)
21
-
22
- ## Introduction
23
-
24
- Gitmark enhances git functionality by introducing the ability to 'mark' commits using the `git mark` command, integrating supported time chains (aka block chains). This process 'reinforces' or 'finalizes' specific commits, facilitating the establishment of a global consensus and creating a definitive, auditable, and tamper-proof history for a project. Git mark additionally solves the double spend problem for git repositories. Technical details are presented in the git mark [SCHEMA](./SCHEMA.md).
25
-
26
- ## Installation
8
+ ## Install
27
9
 
28
10
  ```sh
29
- sudo npm install -g gitmark
11
+ npm install -g gitmark
30
12
  ```
31
13
 
32
- ## Usage
33
-
34
- ```bash
35
- git mark # marks the current commit
36
-
37
- git mark [--genesis txoutput] # used for the genesis commit
38
- ```
39
-
40
- ## Motivation
41
-
42
- Gitmark was originally created to facilitate the [marking](https://github.com/project-bitmark/marking/wiki) use case, which aims to allow global, distributed reputation trees to be grounded in a blockchain.
43
-
44
- What is made possible, is a way to provide consensus on a definitive git branch/chain, in order to ensure that the history has not been tampered with
45
-
46
- One can reconstruct the current state from the history, and this can also be used to preserve your reputation at any given time, say, if any provider ceases to operate
47
-
48
- It's also possible to audit and verify the integrity of the git chain, to create a secure, finalized, state machine, with a definitive head, that is globally synced
49
-
50
- The system can be extended beyond reputation trees, to use any git based store, and anchor it to a secure, verifiable chain of blocks, to determine the definitive history
51
-
52
- Many thanks go to Peter Todd for his work on [single use seals](https://petertodd.org/2017/scalable-single-use-seal-asset-transfer) and Dr Maxim Orlovsky for his work on [RGB](https://rgb-org.github.io/)
53
-
54
- _Gitmark is pre-alpha software, it should be considered experimental, and used at your own risk_
55
-
56
- ## Prerequisites
57
-
58
- Because Gitmark was designed to anchor reputation trees, the reputation of the underlying blockchain must be unimpaired. Gitmark only supports blockchains that are provably fair. Bitcoin is regarded as the most secure and fairest of all blockchains, and should be used for high value projects where cost is not an issue.
59
-
60
- Gitmark does not support projects that are premines, instamines, ICOs, have developer taxes or provably unfair consensus, such as proof of stake.
61
-
62
- In solving the reputation use case, we aim to innovate in the space, contribute back code, and operate as a testing ground.
63
-
64
- The first prerequisite is to obtain an unspent transaction on a supporting blockchain.
65
-
66
- ## Getting started
14
+ This gives you `git mark` as a native git subcommand.
67
15
 
68
- After you have obtained some blockchain currency, send those coins to an address for which you have the key pair. That becomes the genesis unspent transaction
16
+ ## Quick Start
69
17
 
70
- Having created a genesis transaction, and recording the key pair safely, you are ready to start marking!
71
-
72
- Install gitmark globally via npm, the executables will be in the bin directory which can be located with `which git-mark`
73
-
74
- The genesis transaction provides the first input to the `git mark` command, and the private key (secret exponent in hex) is needed to advance the genesis transaction in line with the current git HEAD
75
-
76
- ## Git mark
77
-
78
- Simply running `git mark` on a repo will create your first marking
79
-
80
- You will need a genesis address for the first run of the form `tx:output` and supply that with the argument --genesis [tx]
81
-
82
- You will also need the private key from that tx, which is an argument to the gitmark script, but can be also saved in a location as directed by the output from the git mark command. The key (ie 64 char hex secret exponent) should be stored in the indicated file with the json key "privkey"
83
-
84
- _Warning: do not use the default private key, that is set, in the script!_
85
-
86
- Git mark will generate a new address to send to, a fee, an amount, a spending private key and unspent tx data as inputs to an rpc or a simple script `tx.sh` that lives in the bin directory. Future versions will use a transaction builder to send to a network directly
87
-
88
- After running this script, an empty commit message is generated which you can check in, and points to the latest new unspent transaction, creating a two way link. The commit message is a gitmark [URI](./URI.md)
89
-
90
- Congratulations! You have now marked your first git repo!
91
-
92
- See also: [Example Workflow](./WORKFLOW.md)
93
-
94
- ## How it works
95
-
96
- Gitmark simply uses [single-use seals](https://petertodd.org/2017/scalable-single-use-seal-asset-transfer) to tweak the initial public key address of the genesis transaction by the commit hash of the git tree. The current git hash is added to the original, genesis public key in the output transaction, creating a chain of commits in the blockchain.
97
-
98
- In this way, the blockchain points to git. It then points the next commit back to the blockchain tx, creating a two-way link, and therefore, a strong binding at one particular point in time.
99
-
100
- Similarly, the definitive git tree forms a chain of commits that go forward in time, and so do the new transactions on the blockchain. Further commits are periodically marked in time, proving an auditable trail on-chain of the evolution of the git tree. It also shows the latest confirmed state of a git tree that can be used for trading or in safe or smart contracts.
18
+ ```bash
19
+ # Initialize in a git repo (tbtc4 = Bitcoin testnet4)
20
+ git mark init --chain tbtc4 --voucher txo:tbtc4:txid:vout?amount=X&key=Y
101
21
 
102
- The first use case for gitmark is marking reputation trees, but it can be applied to any git system where the history is important.
22
+ # Make commits, then mark them
23
+ git commit -m "my change"
24
+ git mark
103
25
 
104
- For more technical information take a look at [SCHEMA.md](./SCHEMA.md).
26
+ # Verify the trail against Bitcoin
27
+ git mark verify
105
28
 
106
- ## Recent git marks
29
+ # Show trail state
30
+ git mark info
31
+ ```
107
32
 
108
- - [Github](https://github.com/search?o=desc&q=%22gitmark+%22&s=committer-date&type=Commits)
33
+ ## How It Works
109
34
 
110
- ## Use Cases
35
+ Each `git mark` creates a real Bitcoin transaction. The address is derived from your key + the commit hash using BIP-341 taproot key chaining:
111
36
 
112
- - Distributed Reputation Trees
113
- - Distributed Ledgers
114
- - Registries
115
- - Safe or Smart Contracts
116
- - Asset Issuance
117
- - Distributed Exchanges
118
- - Reconstruct histories from Genesis
119
- - Distributed Global Consensus
120
- - Domain independent web sites
121
- - Archiving and Time Travel through History
122
- - Distributed Identity and PKI
123
- - Federated Side Chains
124
- - Auditing Histories
125
- - Fraud Detection
126
- - Supply Chains
37
+ ```
38
+ baseKey + tweak(commit₁) → Address₁ → TXO₁
39
+ baseKey + tweak(commit₁, commit₂) → Address₂ → TXO₂
40
+ ```
127
41
 
128
- ## Related work
42
+ The chain of addresses on Bitcoin mirrors the chain of commits in git. Anyone can verify by re-deriving the addresses from the public key + commit hashes.
43
+
44
+ ## Commands
45
+
46
+ | Command | Description |
47
+ |---------|-------------|
48
+ | `git mark init` | Initialize trail. Options: `--chain`, `--voucher`, `--force` |
49
+ | `git mark` | Anchor HEAD commit to Bitcoin |
50
+ | `git mark info` | Show trail state, balance, addresses |
51
+ | `git mark verify` | Verify all marks against Bitcoin |
52
+
53
+ ## Trail File
54
+
55
+ `blocktrails.json` in your repo root — committed, visible, verifiable:
56
+
57
+ ```json
58
+ {
59
+ "version": "0.0.3",
60
+ "profile": "gitmark",
61
+ "publicKeyBase": "02abc...",
62
+ "chain": "tbtc4",
63
+ "states": ["a1b2c3", "e5f6a7"],
64
+ "txo": [
65
+ "txo:tbtc4:abc:0?commit=a1b2c3",
66
+ "txo:tbtc4:def:0?commit=e5f6a7"
67
+ ]
68
+ }
69
+ ```
129
70
 
130
- - [Single Use Seals](https://petertodd.org/2017/scalable-single-use-seal-asset-transfer)
131
- - [RGB](https://rgb-org.github.io/)
132
- - [Commerce Block Mainstay](https://www.commerceblock.com/mainstay/) [[White Paper](https://cloudflare-ipfs.com/ipns/ipfs.commerceblock.com/commerceblock-whitepaper-mainstay.pdf)]
133
- - [BIP 175 - Pay to Contract Protocol](https://github.com/bitcoin/bips/blob/master/bip-0175.mediawiki)
134
- - [LRC-20](https://github.com/akitamiabtc/LRC-20/blob/main/LRC_20_V0.1.pdf)
135
- - [DID-BTC](https://microstrategy.github.io/did-btc-spec/)
71
+ - **publicKeyBase** compressed pubkey (02/03 prefix), base for BIP-341 key chaining
72
+ - **states** — commit hashes (input to key derivation)
73
+ - **txo** Bitcoin anchors (TXO URIs, self-contained and verifiable)
136
74
 
137
- ## Source code
75
+ Private spending state stored in `.git/blocktrails.json` (never committed).
138
76
 
139
- - [Source](https://github.com/solidpayorg/gitmark)
140
- - [Issue Tracker](https://github.com/solidpayorg/gitmark/issues)
141
- - [NPM](https://www.npmjs.com/package/gitmark)
77
+ ## Key Storage
142
78
 
143
- ## Future work
79
+ Your private key is stored in git config:
144
80
 
145
- - Git marks can be extended to further layers by using git submodules hence creating almost unlimited space
81
+ ```bash
82
+ git config nostr.privkey <64-char-hex>
83
+ ```
146
84
 
147
- - Private git repositories can be supported out of the box, and given that private keys are used in each seal, encrypted backups can be made
85
+ Same secp256k1 key used for Nostr, Bitcoin, and Solid pod authentication.
148
86
 
149
- - The project git tree can be backed up or archived using git clone in multiple locations. It is natural that popular projects are cloned often in any case
87
+ ## Dependencies
150
88
 
151
- - Seals can be opened and closed using a federation, in order to try out multiple consensus and verification methods
89
+ Two: `@noble/curves` and `@noble/hashes`. No bitcoinjs-lib, no external transaction builders.
152
90
 
153
- - More robust verification frameworks can be built using node testing frameworks, and continuous integration, tho currently the distribution contains a git-mark-verify script
91
+ ## Related
154
92
 
155
- - Lightweight Autonomous Marking Agents (LAMAs) can be created that listen to communities for marks and just work, without needing a human operator. The service can be deployed on a server or container, and be designed to bring itself up if it goes down in any one location
93
+ - [blocktrails.org](https://blocktrails.org) state anchoring on Bitcoin
94
+ - [git-mark.com](https://git-mark.com) — project homepage
95
+ - [BIP-341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki) — Taproot key tweaking
156
96
 
157
97
  ## License
158
98
 
159
- - MIT
99
+ AGPL-3.0 &copy; Melvin Carvalho
package/bin/git-mark.js CHANGED
@@ -425,32 +425,40 @@ async function cmdVerify() {
425
425
  process.exit(ok ? 0 : 1);
426
426
  }
427
427
 
428
+ // --- Exports for testing ---
429
+ export {
430
+ taggedHash, btScalar, deriveChainedPrivkey, deriveChainedPubkey,
431
+ pubkeyToAddress, parseTxoUri, p2trScript, buildTransaction,
432
+ TRAIL_FILE, PRIVATE_FILE, CHAINS
433
+ };
434
+
428
435
  // --- CLI ---
429
- const args = process.argv.slice(2);
430
- const cmd = args[0];
431
-
432
- if (cmd === 'init') {
433
- cmdInit(args.slice(1));
434
- } else if (cmd === 'info') {
435
- cmdInfo();
436
- } else if (cmd === 'verify') {
437
- cmdVerify();
438
- } else if (cmd === 'mark' || !cmd || (cmd && !cmd.startsWith('-'))) {
439
- // Default action is mark (git mark = git mark mark)
440
- // But if no trail exists, suggest init
441
- if (!existsSync(TRAIL_FILE) && cmd !== 'mark') {
436
+ const isMain = process.argv[1]?.endsWith('git-mark.js') || process.argv[1]?.endsWith('git-mark');
437
+ if (isMain) {
438
+ const args = process.argv.slice(2);
439
+ const cmd = args[0];
440
+
441
+ if (cmd === 'init') {
442
+ cmdInit(args.slice(1));
443
+ } else if (cmd === 'info') {
444
+ cmdInfo();
445
+ } else if (cmd === 'verify') {
446
+ cmdVerify();
447
+ } else if (cmd === 'mark' || !cmd || (cmd && !cmd.startsWith('-'))) {
448
+ if (!existsSync(TRAIL_FILE) && cmd !== 'mark') {
449
+ console.log('Usage:');
450
+ console.log(' git mark init [--chain tbtc4] [--voucher txo:...]');
451
+ console.log(' git mark # anchor HEAD to Bitcoin');
452
+ console.log(' git mark info # show trail state');
453
+ console.log(' git mark verify # verify trail against Bitcoin');
454
+ } else {
455
+ cmdMark(args.slice(cmd === 'mark' ? 1 : 0));
456
+ }
457
+ } else {
442
458
  console.log('Usage:');
443
459
  console.log(' git mark init [--chain tbtc4] [--voucher txo:...]');
444
460
  console.log(' git mark # anchor HEAD to Bitcoin');
445
461
  console.log(' git mark info # show trail state');
446
462
  console.log(' git mark verify # verify trail against Bitcoin');
447
- } else {
448
- cmdMark(args.slice(cmd === 'mark' ? 1 : 0));
449
463
  }
450
- } else {
451
- console.log('Usage:');
452
- console.log(' git mark init [--chain tbtc4] [--voucher txo:...]');
453
- console.log(' git mark # anchor HEAD to Bitcoin');
454
- console.log(' git mark info # show trail state');
455
- console.log(' git mark verify # verify trail against Bitcoin');
456
464
  }
package/index.html ADDED
@@ -0,0 +1,141 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>git mark — Anchor Git Commits to Bitcoin</title>
7
+ <meta name="description" content="Mark your git commits on Bitcoin using blocktrails key chaining. Tamper-proof history, globally verifiable, two dependencies.">
8
+ <meta property="og:title" content="git mark — Anchor Git Commits to Bitcoin">
9
+ <meta property="og:description" content="Mark your git commits on Bitcoin using blocktrails key chaining. Tamper-proof history, globally verifiable.">
10
+ <meta property="og:type" content="website">
11
+ <meta property="og:url" content="https://git-mark.com">
12
+ <style>
13
+ * { margin: 0; padding: 0; box-sizing: border-box; }
14
+ body { font-family: Georgia, 'Times New Roman', serif; background: #fafaf8; color: #2c2c2c; line-height: 1.7; padding: 2rem; }
15
+ .container { max-width: 720px; margin: 0 auto; }
16
+ h1 { font-size: 2.2rem; font-weight: 400; margin-bottom: 0.25rem; }
17
+ .subtitle { color: #666; font-size: 1.1rem; margin-bottom: 0.5rem; }
18
+ .meta { color: #999; font-size: 0.85rem; margin-bottom: 2rem; padding-bottom: 1.5rem; border-bottom: 1px solid #ddd; }
19
+ .meta a { color: #999; }
20
+ h2 { font-size: 1.3rem; color: #f7931a; margin: 2.5rem 0 1rem; font-weight: 600; }
21
+ p { margin-bottom: 1.25rem; }
22
+ code { background: #f0efeb; padding: 0.15rem 0.4rem; border-radius: 3px; font-family: 'SFMono-Regular', Consolas, monospace; font-size: 0.85em; color: #555; }
23
+ pre { background: #f0efeb; padding: 1rem; border-radius: 4px; overflow-x: auto; font-family: 'SFMono-Regular', Consolas, monospace; font-size: 0.85rem; margin: 1.25rem 0; line-height: 1.5; }
24
+ a { color: #1a5276; }
25
+ strong { font-weight: 600; }
26
+ table { width: 100%; border-collapse: collapse; margin: 1.5rem 0; font-size: 0.9rem; }
27
+ th { text-align: left; padding: 0.6rem 0.75rem; border-bottom: 2px solid #ddd; color: #888; font-weight: 500; }
28
+ td { padding: 0.5rem 0.75rem; border-bottom: 1px solid #eee; }
29
+ .highlight { color: #f7931a; font-weight: 600; }
30
+ .callout { background: #fff8f0; border: 1px solid #f7931a33; border-radius: 4px; padding: 1.25rem; margin: 1.5rem 0; }
31
+ .callout .label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.1em; color: #f7931a; margin-bottom: 0.5rem; }
32
+ footer { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid #ddd; color: #999; font-size: 0.85rem; }
33
+ footer a { color: #888; }
34
+ </style>
35
+ </head>
36
+ <body>
37
+ <div class="container">
38
+
39
+ <h1>git mark</h1>
40
+ <div class="subtitle">Anchor git commits to Bitcoin via blocktrails</div>
41
+ <div class="meta">
42
+ <a href="https://github.com/solidpayorg/gitmark">GitHub</a> &middot;
43
+ <a href="https://www.npmjs.com/package/gitmark">npm</a> &middot;
44
+ <a href="https://blocktrails.org">blocktrails.org</a>
45
+ </div>
46
+
47
+ <h2>What It Does</h2>
48
+
49
+ <p><span class="highlight">Every <code>git mark</code> creates a Bitcoin transaction</span> that anchors your current commit to the blockchain. The transaction address is derived from your key + the commit hash using BIP-341 key chaining. Anyone can verify the anchor. Nobody can tamper with it.</p>
50
+
51
+ <p>The result is a two-way link: the blockchain follows your repo (via tweaked addresses), and your repo follows the blockchain (via <code>blocktrails.json</code>).</p>
52
+
53
+ <h2>Install</h2>
54
+
55
+ <pre>npm install -g gitmark</pre>
56
+
57
+ <p>This gives you <code>git mark</code> as a native git subcommand.</p>
58
+
59
+ <h2>Quick Start</h2>
60
+
61
+ <pre># Initialize in a git repo
62
+ git mark init --chain tbtc4 --voucher txo:tbtc4:txid:vout?amount=X&amp;key=Y
63
+
64
+ # Make commits, then mark them
65
+ git commit -m "my change"
66
+ git mark
67
+
68
+ # Verify the trail
69
+ git mark verify
70
+
71
+ # Show trail state
72
+ git mark info</pre>
73
+
74
+ <h2>How It Works</h2>
75
+
76
+ <table>
77
+ <tr><th>Step</th><th>What Happens</th></tr>
78
+ <tr><td>Init</td><td>Generates or reads your Nostr key, creates <code>blocktrails.json</code>, optionally funds the trail</td></tr>
79
+ <tr><td>Mark</td><td>Takes HEAD commit hash, derives a tweaked taproot address via BIP-341, builds and broadcasts a Bitcoin transaction</td></tr>
80
+ <tr><td>Verify</td><td>Walks the trail, re-derives each address from the public key + commit hashes, checks each transaction on Bitcoin</td></tr>
81
+ </table>
82
+
83
+ <p>Each mark advances the key chain: <code>newKey = baseKey + tweak(commit_hash)</code>. The chain of derived addresses on Bitcoin mirrors the chain of commits in git.</p>
84
+
85
+ <h2>The Trail File</h2>
86
+
87
+ <p><code>blocktrails.json</code> lives in your repo root, committed and visible:</p>
88
+
89
+ <pre>{
90
+ "version": "0.0.3",
91
+ "profile": "gitmark",
92
+ "publicKeyBase": "02abc...",
93
+ "chain": "tbtc4",
94
+ "states": ["a1b2c3...", "e5f6a7..."],
95
+ "txo": [
96
+ "txo:tbtc4:abc:0?commit=a1b2c3...",
97
+ "txo:tbtc4:def:0?commit=e5f6a7..."
98
+ ]
99
+ }</pre>
100
+
101
+ <p><strong>states</strong> — the commit hashes (input to key chaining math). <strong>txo</strong> — the Bitcoin anchors (TXO URIs, self-contained and verifiable). Anyone who clones your repo can verify the trail independently.</p>
102
+
103
+ <h2>Key Storage</h2>
104
+
105
+ <p>Your private key is stored in git config:</p>
106
+
107
+ <pre>git config nostr.privkey &lt;64-char-hex&gt;</pre>
108
+
109
+ <p>The same secp256k1 key you use for Nostr, Bitcoin, and Solid pod authentication. One key for everything.</p>
110
+
111
+ <div class="callout">
112
+ <div class="label">Blocktrails</div>
113
+ <p style="margin-bottom: 0.75rem">Git-mark is a <a href="https://blocktrails.org">blocktrails</a> application profile. The key chaining math is identical to <a href="https://blocktrails.org">MRC20 tokens</a> — just with commit hashes as state instead of token ledgers. A generic blocktrails verifier handles both.</p>
114
+ <p style="margin-bottom: 0">Two dependencies: <code>@noble/curves</code> and <code>@noble/hashes</code>. No bitcoinjs-lib. No external transaction builders. Pure secp256k1 math.</p>
115
+ </div>
116
+
117
+ <h2>Commands</h2>
118
+
119
+ <table>
120
+ <tr><th>Command</th><th>Description</th></tr>
121
+ <tr><td><code>git mark init</code></td><td>Initialize trail. Options: <code>--chain</code>, <code>--voucher</code>, <code>--force</code></td></tr>
122
+ <tr><td><code>git mark</code></td><td>Anchor HEAD commit to Bitcoin</td></tr>
123
+ <tr><td><code>git mark info</code></td><td>Show trail state, balance, addresses</td></tr>
124
+ <tr><td><code>git mark verify</code></td><td>Verify all marks against Bitcoin</td></tr>
125
+ </table>
126
+
127
+ <h2>Related</h2>
128
+
129
+ <p>
130
+ <a href="https://blocktrails.org">Blocktrails</a> &mdash; state anchoring on Bitcoin<br>
131
+ <a href="https://github.com/nicepkg/gitmark-test">gitmark-test</a> &mdash; interim implementation<br>
132
+ <a href="https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki">BIP-341</a> &mdash; Taproot key tweaking
133
+ </p>
134
+
135
+ <footer>
136
+ <p>&copy; 2021-2026 <a href="https://melvin.me">Melvin Carvalho</a> &middot; AGPL-3.0 &middot; <a href="https://github.com/solidpayorg/gitmark">Source</a></p>
137
+ </footer>
138
+
139
+ </div>
140
+ </body>
141
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitmark",
3
- "version": "0.0.69",
3
+ "version": "0.0.71",
4
4
  "type": "module",
5
5
  "description": "Anchor git commits to Bitcoin via blocktrails",
6
6
  "main": "bin/git-mark.js",
@@ -8,7 +8,7 @@
8
8
  "git-mark": "bin/git-mark.js"
9
9
  },
10
10
  "scripts": {
11
- "test": "node bin/git-mark.js --help"
11
+ "test": "node --test test/git-mark.test.js"
12
12
  },
13
13
  "repository": {
14
14
  "type": "git",
@@ -22,7 +22,7 @@
22
22
  "nostr"
23
23
  ],
24
24
  "author": "Melvin Carvalho",
25
- "license": "MIT",
25
+ "license": "AGPL-3.0-or-later",
26
26
  "bugs": {
27
27
  "url": "https://github.com/solidpayorg/gitmark/issues"
28
28
  },
@@ -0,0 +1,191 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { secp256k1 } from '@noble/curves/secp256k1';
4
+ import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
5
+
6
+ import {
7
+ taggedHash, btScalar, deriveChainedPrivkey, deriveChainedPubkey,
8
+ pubkeyToAddress, parseTxoUri, p2trScript, CHAINS
9
+ } from '../bin/git-mark.js';
10
+
11
+ describe('Key chaining', () => {
12
+ const privkey = hexToBytes('0000000000000000000000000000000000000000000000000000000000000001');
13
+ const pubkey = secp256k1.getPublicKey(privkey, true);
14
+
15
+ it('deriveChainedPubkey with no states returns original key', () => {
16
+ const result = deriveChainedPubkey(pubkey, []);
17
+ assert.deepStrictEqual(result, pubkey);
18
+ });
19
+
20
+ it('deriveChainedPubkey with one state changes the key', () => {
21
+ const result = deriveChainedPubkey(pubkey, ['abc123']);
22
+ assert.notDeepStrictEqual(result, pubkey);
23
+ assert.strictEqual(result.length, 33); // compressed
24
+ assert.ok(result[0] === 0x02 || result[0] === 0x03); // valid prefix
25
+ });
26
+
27
+ it('deriveChainedPubkey with two states differs from one state', () => {
28
+ const one = deriveChainedPubkey(pubkey, ['abc123']);
29
+ const two = deriveChainedPubkey(pubkey, ['abc123', 'def456']);
30
+ assert.notDeepStrictEqual(one, two);
31
+ });
32
+
33
+ it('deriveChainedPrivkey matches deriveChainedPubkey', () => {
34
+ const states = ['commit1', 'commit2', 'commit3'];
35
+ const derivedPriv = deriveChainedPrivkey(privkey, states);
36
+ const derivedPub = deriveChainedPubkey(pubkey, states);
37
+ const pubFromPriv = secp256k1.getPublicKey(derivedPriv, true);
38
+ assert.deepStrictEqual(new Uint8Array(pubFromPriv), new Uint8Array(derivedPub));
39
+ });
40
+
41
+ it('order of states matters', () => {
42
+ const a = deriveChainedPubkey(pubkey, ['first', 'second']);
43
+ const b = deriveChainedPubkey(pubkey, ['second', 'first']);
44
+ assert.notDeepStrictEqual(a, b);
45
+ });
46
+
47
+ it('different keys produce different results for same states', () => {
48
+ const privkey2 = hexToBytes('0000000000000000000000000000000000000000000000000000000000000002');
49
+ const pubkey2 = secp256k1.getPublicKey(privkey2, true);
50
+ const a = deriveChainedPubkey(pubkey, ['state1']);
51
+ const b = deriveChainedPubkey(pubkey2, ['state1']);
52
+ assert.notDeepStrictEqual(a, b);
53
+ });
54
+ });
55
+
56
+ describe('Address derivation', () => {
57
+ const privkey = secp256k1.utils.randomPrivateKey();
58
+ const pubkey = bytesToHex(secp256k1.getPublicKey(privkey, true));
59
+
60
+ it('generates valid testnet4 address', () => {
61
+ const addr = pubkeyToAddress(pubkey, [], 'tbtc4');
62
+ assert.ok(addr.startsWith('tb1p'));
63
+ assert.strictEqual(addr.length, 62); // bech32m P2TR
64
+ });
65
+
66
+ it('generates valid testnet3 address', () => {
67
+ const addr = pubkeyToAddress(pubkey, [], 'tbtc3');
68
+ assert.ok(addr.startsWith('tb1p'));
69
+ });
70
+
71
+ it('generates valid mainnet address', () => {
72
+ const addr = pubkeyToAddress(pubkey, [], 'btc');
73
+ assert.ok(addr.startsWith('bc1p'));
74
+ });
75
+
76
+ it('different states produce different addresses', () => {
77
+ const a = pubkeyToAddress(pubkey, [], 'tbtc4');
78
+ const b = pubkeyToAddress(pubkey, ['commit1'], 'tbtc4');
79
+ assert.notStrictEqual(a, b);
80
+ });
81
+
82
+ it('same inputs produce same address (deterministic)', () => {
83
+ const a = pubkeyToAddress(pubkey, ['commit1'], 'tbtc4');
84
+ const b = pubkeyToAddress(pubkey, ['commit1'], 'tbtc4');
85
+ assert.strictEqual(a, b);
86
+ });
87
+ });
88
+
89
+ describe('TXO URI parsing', () => {
90
+ it('parses basic TXO URI', () => {
91
+ const result = parseTxoUri('txo:tbtc4:abc123:0');
92
+ assert.strictEqual(result.chain, 'tbtc4');
93
+ assert.strictEqual(result.txid, 'abc123');
94
+ assert.strictEqual(result.vout, 0);
95
+ });
96
+
97
+ it('parses TXO URI with query params', () => {
98
+ const result = parseTxoUri('txo:tbtc4:abc123:1?amount=10000&key=deadbeef');
99
+ assert.strictEqual(result.vout, 1);
100
+ assert.strictEqual(result.amount, 10000);
101
+ assert.strictEqual(result.key, 'deadbeef');
102
+ });
103
+
104
+ it('parses TXO URI with commit param', () => {
105
+ const result = parseTxoUri('txo:tbtc4:abc123:0?commit=a1b2c3d4');
106
+ assert.strictEqual(result.chain, 'tbtc4');
107
+ assert.strictEqual(result.txid, 'abc123');
108
+ });
109
+
110
+ it('throws on invalid URI', () => {
111
+ assert.throws(() => parseTxoUri('not-a-txo'), /Invalid TXO URI/);
112
+ });
113
+ });
114
+
115
+ describe('taggedHash', () => {
116
+ it('produces 32 bytes', () => {
117
+ const result = taggedHash('TapTweak', new Uint8Array([1, 2, 3]));
118
+ assert.strictEqual(result.length, 32);
119
+ });
120
+
121
+ it('is deterministic', () => {
122
+ const a = taggedHash('TapTweak', new Uint8Array([1, 2, 3]));
123
+ const b = taggedHash('TapTweak', new Uint8Array([1, 2, 3]));
124
+ assert.deepStrictEqual(a, b);
125
+ });
126
+
127
+ it('different tags produce different hashes', () => {
128
+ const a = taggedHash('TapTweak', new Uint8Array([1]));
129
+ const b = taggedHash('TapSighash', new Uint8Array([1]));
130
+ assert.notDeepStrictEqual(a, b);
131
+ });
132
+ });
133
+
134
+ describe('p2trScript', () => {
135
+ it('produces correct script', () => {
136
+ const xonly = new Uint8Array(32).fill(0xab);
137
+ const script = p2trScript(xonly);
138
+ assert.strictEqual(script.length, 34);
139
+ assert.strictEqual(script[0], 0x51); // OP_1
140
+ assert.strictEqual(script[1], 0x20); // push 32 bytes
141
+ assert.deepStrictEqual(script.slice(2), xonly);
142
+ });
143
+ });
144
+
145
+ describe('Chain registry', () => {
146
+ it('has tbtc4', () => {
147
+ assert.ok(CHAINS.tbtc4);
148
+ assert.ok(CHAINS.tbtc4.explorer.includes('testnet4'));
149
+ });
150
+
151
+ it('has tbtc3', () => {
152
+ assert.ok(CHAINS.tbtc3);
153
+ assert.ok(CHAINS.tbtc3.explorer.includes('testnet'));
154
+ });
155
+
156
+ it('has btc', () => {
157
+ assert.ok(CHAINS.btc);
158
+ assert.ok(CHAINS.btc.explorer.includes('mempool.space/api'));
159
+ });
160
+ });
161
+
162
+ describe('Trail format', () => {
163
+ it('creates valid trail structure', () => {
164
+ const privkey = secp256k1.utils.randomPrivateKey();
165
+ const pubkey = bytesToHex(secp256k1.getPublicKey(privkey, true));
166
+
167
+ const trail = {
168
+ version: '0.0.3',
169
+ profile: 'gitmark',
170
+ publicKeyBase: pubkey,
171
+ chain: 'tbtc4',
172
+ states: [],
173
+ txo: []
174
+ };
175
+
176
+ assert.strictEqual(trail.version, '0.0.3');
177
+ assert.strictEqual(trail.profile, 'gitmark');
178
+ assert.strictEqual(trail.publicKeyBase.length, 66); // compressed hex
179
+ assert.ok(trail.publicKeyBase.startsWith('02') || trail.publicKeyBase.startsWith('03'));
180
+ assert.ok(Array.isArray(trail.states));
181
+ assert.ok(Array.isArray(trail.txo));
182
+ });
183
+
184
+ it('states and txo stay parallel after marks', () => {
185
+ const trail = {
186
+ states: ['commit1', 'commit2'],
187
+ txo: ['txo:tbtc4:abc:0?commit=commit1', 'txo:tbtc4:def:0?commit=commit2']
188
+ };
189
+ assert.strictEqual(trail.states.length, trail.txo.length);
190
+ });
191
+ });