Skip to content

Commit

Permalink
Merge pull request #247 from ArweaveTeam/get-ec-owner
Browse files Browse the repository at this point in the history
calculate ecdsa signed tx's owner & address
  • Loading branch information
rosmcmahon authored Feb 22, 2025
2 parents 8ed093a + aa1b43f commit 974818c
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 67 deletions.
32 changes: 6 additions & 26 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "arweave",
"version": "2.0.0-ec.0",
"version": "2.0.0-ec.1",
"description": "Arweave JS client library",
"main": "./node/index.js",
"react-native": "./node/index.js",
Expand Down Expand Up @@ -100,7 +100,7 @@
"node": ">=18"
},
"dependencies": {
"@arweave/wasm-secp256k1": "^0.0.6",
"@arweave/wasm-secp256k1": "^0.0.7",
"arconnect": "^0.4.2",
"asn1.js": "^5.4.1",
"base64-js": "^1.5.1",
Expand Down
1 change: 1 addition & 0 deletions src/common/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ export default class Arweave {
return siloTransaction;
}

/** @deprecated use GQL https://gql-guide.arweave.net */
public arql(query: object): Promise<string[]> {
return this.api
.post("/arql", query)
Expand Down
53 changes: 49 additions & 4 deletions src/common/lib/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as ArweaveUtils from "./utils";
import deepHash from "./deepHash";
import { Chunk, Proof, generateTransactionChunks } from "./merkle";
import { SECP256k1PublicKey } from "./crypto/keys";
import WebCryptoDriver from './crypto/webcrypto-driver'

class BaseObject {
[key: string]: any;
Expand Down Expand Up @@ -86,14 +88,13 @@ export interface TransactionInterface {
data_root: string;
}

export default class Transaction
extends BaseObject
implements TransactionInterface
{
export default class Transaction extends BaseObject implements TransactionInterface {
public readonly format: number = 2;
public id: string = "";
public readonly last_tx: string = "";
public owner: string = "";
private _owner: string = "";
private _address: string = "";
public tags: Tag[] = [];
public readonly target: string = "";
public readonly quantity: string = "0";
Expand Down Expand Up @@ -274,4 +275,48 @@ export default class Transaction
throw new Error(`Unexpected transaction format: ${this.format}`);
}
}

public async getOwner(): Promise<string> {
// if owner already set or calculated, return value
if (this.owner) return this.owner;
if (this._owner) return this._owner;

if (!this.signature) throw new Error('cannot get owner, transaction is not signed.');

// ecdsa signing key as (this.owner = "")
const payload = await this.getSignatureData();
const ecPubKey = await SECP256k1PublicKey.recover({
payload,
isDigest: false,
signature: ArweaveUtils.b64UrlToBuffer(this.signature),
});

return this._owner = ArweaveUtils.bufferTob64Url(await ecPubKey.identifier());
}

public async getOwnerAddress(): Promise<string> {
if (this._address) return this._address;

if (!this.signature) throw new Error('cannot get owner address, transaction is not signed.');

let rawOwner: Uint8Array;
if (this.owner) {
rawOwner = ArweaveUtils.b64UrlToBuffer(this.owner);
} else if (this._owner) {
rawOwner = ArweaveUtils.b64UrlToBuffer(this._owner);
} else {
const payload = await this.getSignatureData();
const ecPubKey = await SECP256k1PublicKey.recover({
payload,
isDigest: false,
signature: ArweaveUtils.b64UrlToBuffer(this.signature),
});
rawOwner = await ecPubKey.identifier();
}

return this._address = ArweaveUtils.bufferTob64Url(
await (new WebCryptoDriver).hash(rawOwner)
);
}

}
18 changes: 18 additions & 0 deletions test/fixtures/signed_ecdsa_tx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"format": 2,
"id": "CODy9eol1Y70rm42BPucVxvacAXqfgm-Ktn6t-KqfTo",
"last_tx": "6N84-ZsgAWMbiqZrzi0-sviG5FudEY1pwzxehG10YcGuOl2e8pfSW8OTfI31qGHT",
"owner": "",
"tags": [ {
"name": "VGVzdA",
"value": "ZWNkc2EtdHg"
} ],
"target": "Qa8AAZv-sEhQRIm7xZr3CVLtlzIH8NezaY0GZhURcAc",
"quantity": "1",
"data": "",
"data_size": "0",
"data_tree": [ ],
"data_root": "",
"reward": "3377063",
"signature": "PrPcovUN6PdzQxRcWMgj1m27IJPEHslC4xaOtYHUGKUEaCEpgaw9toeMVdYEk1rhwMlNKtBkT38MAH81xSIYbAE"
}
9 changes: 9 additions & 0 deletions test/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,4 +292,13 @@ describe("Transactions", function () {
expect(dataRoot).to.equal(expectedDataRoot);
expect(tx.signature).to.equal(expectedSignature);
});

it("should get the correct owner address from an ecdsa transaction", async () => {
const txJson = require('./fixtures/signed_ecdsa_tx.json')
const tx = arweave.transactions.fromRaw(txJson)

const addr = await tx.getOwnerAddress()
expect(addr).equal("RymI02hes920xGugzRJ3L54eGg-jVU-_R2uCI057_nU")
});

});
27 changes: 25 additions & 2 deletions test/web/web.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as chai from "chai";
import * as crypto from "crypto";
import Arweave from "../../web";
import { bufferToString, stringToBuffer, b64UrlToBuffer } from "../../src/common/lib/utils";

Expand Down Expand Up @@ -259,6 +258,30 @@ describe("Transactions", function () {
.to.be.an("array")
.which.contains("Sgmyo7nUqPpVQWUfK72p5yIpd85QQbhGaWAF-I8L6yE");
});

it("should determine correct owner address of ecdsa tx", async () => {
const rawTx = {
"format": 2,
"id": "CODy9eol1Y70rm42BPucVxvacAXqfgm-Ktn6t-KqfTo",
"last_tx": "6N84-ZsgAWMbiqZrzi0-sviG5FudEY1pwzxehG10YcGuOl2e8pfSW8OTfI31qGHT",
"owner": "",
"tags": [{
"name": "VGVzdA",
"value": "ZWNkc2EtdHg"
}],
"target": "Qa8AAZv-sEhQRIm7xZr3CVLtlzIH8NezaY0GZhURcAc",
"quantity": "1",
"data": "",
"data_size": "0",
"data_tree": [],
"data_root": "",
"reward": "3377063",
"signature": "PrPcovUN6PdzQxRcWMgj1m27IJPEHslC4xaOtYHUGKUEaCEpgaw9toeMVdYEk1rhwMlNKtBkT38MAH81xSIYbAE"
}
const tx = arweave.transactions.fromRaw(rawTx)
const addr = await tx.getOwnerAddress()
expect(addr).equal("RymI02hes920xGugzRJ3L54eGg-jVU-_R2uCI057_nU")
});
});

describe("Encryption", function () {
Expand All @@ -269,7 +292,7 @@ describe("Encryption", function () {

const data = stringToBuffer(text);

const key = crypto.randomBytes(32);
const key = crypto.getRandomValues(new Uint8Array(32));

const encrypted = await arweave.crypto.encrypt(data, key);

Expand Down
33 changes: 0 additions & 33 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,16 @@ config.web = {
},
resolve: {
alias: {
process: "process/browser",
crypto: "crypto-browserify",
stream: "stream-browserify",
"@crypto/node-driver": path.resolve(
__dirname,
"./web/lib/crypto/webcrypto-driver"
),
},
fallback: {
process: require.resolve("process/browser"),
crypto: require.resolve("crypto-browserify"),
stream: require.resolve("stream-browserify"),
buffer: require.resolve('buffer/')
},
extensions: [".ts", ".js"],
},
plugins: [
new webpack.ProvidePlugin({
process: "process/browser",
Buffer: ['buffer', 'Buffer']
}),
],
output: {
filename: "web.bundle.js",
Expand All @@ -52,27 +41,16 @@ config.webprod = {
},
resolve: {
alias: {
process: "process/browser",
crypto: "crypto-browserify",
stream: "stream-browserify",
"@crypto/node-driver": path.resolve(
__dirname,
"./web/lib/crypto/webcrypto-driver"
),
},
fallback: {
process: require.resolve("process/browser"),
crypto: require.resolve("crypto-browserify"),
stream: require.resolve("stream-browserify"),
buffer: require.resolve('buffer/')
},
extensions: [".ts", ".js"],
},
plugins: [
new webpack.ProvidePlugin({
process: "process/browser",
Buffer: ['buffer', 'Buffer']
}),
],
optimization: {
minimize: true,
Expand Down Expand Up @@ -100,26 +78,15 @@ config.webtests = {
resolve: {
extensions: [".tsx", ".ts", ".js"],
alias: {
process: "process/browser",
"@crypto/node-driver": path.resolve(
__dirname,
"./web/lib/crypto/webcrypto-driver"
),
},
fallback: {
process: require.resolve("process/browser"),
crypto: require.resolve("crypto-browserify"),
stream: require.resolve("stream-browserify"),
buffer: require.resolve('buffer/')
},
},
plugins: [
new webpack.ProvidePlugin({
process: "process/browser",
crypto: "crypto-browserify",
stream: "stream-browserify",
Buffer: ['buffer', 'Buffer']
}),
],
devtool: "inline-source-map",
devServer: {
Expand Down

0 comments on commit 974818c

Please sign in to comment.